diff --git a/cli/TODO.txt b/cli/TODO.txt index 6d67f07..c048358 100644 --- a/cli/TODO.txt +++ b/cli/TODO.txt @@ -9,6 +9,7 @@ - In keyword. - Structs (maybe enums). - Implement file IO (require structs). +- Implement utf8 support. - Implement gdb like debugger (add color print for readability). - Implement fiber from script body and vm run fibers (not scripts). Then remove vm's root script. diff --git a/docs/pages/Getting-Started/learn-in-15-minutes.md b/docs/pages/Getting-Started/learn-in-15-minutes.md index 50bb7d7..0dedc7e 100644 --- a/docs/pages/Getting-Started/learn-in-15-minutes.md +++ b/docs/pages/Getting-Started/learn-in-15-minutes.md @@ -23,7 +23,6 @@ true; false # Booleans. [42, 'foo', null] # Lists. { 'Key':'value' } # Maps. func(x) return x*x end # Lambda/literal functions. -import lang # Module (imported scripts). # Control flow. # ------------- @@ -71,9 +70,20 @@ end # Concanative call operator '->' -str_lower(str_strip('FOO ')) # This can be written as below -'FOO ' -> str_strip -> str_lower +str_lower(str_strip('Foo ')) # This can be written as below +'Foo ' -> str_strip -> str_lower 'foo' -> print # similer as print('foo') -``` \ No newline at end of file +# Fibers & Coroutine +#------------------- + +def fn(p1, p2) + print(yield(42)) # Prints 3.14 +end + +fb = fiber_new(fn) +val = fiber_run(fb, 1, 2) +print(val) ## Prints 42 +fiber_resume(fb, 3.14) +``` diff --git a/docs/pages/Language-API/fibers.md b/docs/pages/Language-API/fibers.md index a669efe..865fd09 100644 --- a/docs/pages/Language-API/fibers.md +++ b/docs/pages/Language-API/fibers.md @@ -1,4 +1,88 @@ # %% Fibers %% -TODO +Pocketlang support coroutines via [fibers](https://en.wikipedia.org/wiki/Fiber_(computer_science)) +(light weight threads with cooperative multitask). A fiber object is a wrapper +around a function, contains the execution state (simply the stack and the +instruction pointer) for that function, which can be run and once yielded +resumed. + +```ruby +def fn(h, w) + print(h, w) +end + +fb = fiber_new(fn) # Create a fiber. +fiber_run(fb, 'hello', 'world') # Run the fiber. +``` + +## %% Yielding %% + +When a function is yielded, it's state will be stored in the fiber it's +belongs to and will return from the function, to parent fiber it's running +from (not the caller of the function). And you can pass values between +fibers when they yield. + +```ruby +def fn() + print('running') + yield() + print('resumed') +end + +fb = fiber_new(fn) +fiber_run(fb) # Prints 'running'. +print('before resumed') +fiber_resume(fb) # Prints 'resumed'. +``` + +Yield from the fiber with a value. + +```ruby +def fn() + print('running') + yield(42) # Return 42. + print('resumed') +end + +fb = fiber_new(fn) +val = fiber_run(fb) # Prints 'running'. +print(val) # Prints 42. +fiber_resume(fb) # Prints 'resumed'. +``` + +Resume the fiber with a value. + +```ruby +def fn() + print('running') + val = yield() # Resumed value. + print(val) # Prints 42. + print('resumed') +end + +fb = fiber_new(fn) +val = fiber_run(fb) # Prints 'running'. +fiber_resume(fb, 42) # Resume with 42, Prints 'resumed'. +``` + +Once a fiber is done execution, trying to resume it will cause a runtime +error. To check if the fiber is finished it's execution use the function +`fiber_is_done` and use the function `fiber_get_func` to get it's function, +which could be used to create a new fiber to "re-start" the fiber. + +```ruby +fiber_run(fb = fiber_new( +func() + for i in 0..5 do + yield(i) + end +end)) + +while not fiber_is_done(fb) + fiber_resume(fb) +end + +# Get the function from the fiber. +fn = fiber_get_func(fb) +``` diff --git a/docs/try/main.c b/docs/try/main.c index 7530abb..8d0dcc9 100644 --- a/docs/try/main.c +++ b/docs/try/main.c @@ -44,7 +44,10 @@ int runSource(const char* source) { config.resolve_path_fn = resolvePath; PKVM* vm = pkNewVM(&config); - PkInterpretResult result = pkInterpretSource(vm, source, "@try"); + + PkStringPtr src = { source, NULL, NULL }; + PkStringPtr module = { "@try", NULL, NULL }; + PkInterpretResult result = pkInterpretSource(vm, src, module); pkFreeVM(vm); diff --git a/src/buffers.c b/src/buffers.c deleted file mode 100644 index 14cae90..0000000 --- a/src/buffers.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2021 Thakee Nathees - * Licensed under: MIT License - */ - -#include "buffers.h" - -#include "utils.h" -#include "vm.h" - -// The buffer "template" implementation of diferent types, defined in the -// buffers.h header. - -#define DEFINE_BUFFER(m_name, m_name_l, m_type) \ - void m_name_l##BufferInit(m_name##Buffer* self) { \ - self->data = NULL; \ - self->count = 0; \ - self->capacity = 0; \ - } \ - \ - void m_name_l##BufferClear(m_name##Buffer* self, \ - PKVM* vm) { \ - vmRealloc(vm, self->data, self->capacity * sizeof(m_type), 0); \ - self->data = NULL; \ - self->count = 0; \ - self->capacity = 0; \ - } \ - \ - void m_name_l##BufferReserve(m_name##Buffer* self, PKVM* vm, size_t size) { \ - if (self->capacity < size) { \ - int capacity = utilPowerOf2Ceil((int)size); \ - if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; \ - self->data = (m_type*)vmRealloc(vm, self->data, \ - self->capacity * sizeof(m_type), capacity * sizeof(m_type)); \ - self->capacity = capacity; \ - } \ - } \ - \ - void m_name_l##BufferFill(m_name##Buffer* self, PKVM* vm, \ - m_type data, int count) { \ - \ - m_name_l##BufferReserve(self, vm, self->count + count); \ - \ - for (int i = 0; i < count; i++) { \ - self->data[self->count++] = data; \ - } \ - } \ - \ - void m_name_l##BufferWrite(m_name##Buffer* self, \ - PKVM* vm, m_type data) { \ - m_name_l##BufferFill(self, vm, data, 1); \ - } \ - -void byteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str, - uint32_t length) { - byteBufferReserve(self, vm, self->count + length); - for (uint32_t i = 0; i < length; i++) { - self->data[self->count++] = *(str++); - } -} - -DEFINE_BUFFER(Uint, uint, uint32_t) -DEFINE_BUFFER(Byte, byte, uint8_t) -DEFINE_BUFFER(Var, var, Var) -DEFINE_BUFFER(String, string, String*) -DEFINE_BUFFER(Function, function, Function*) - -#undef DEFINE_BUFFER diff --git a/src/buffers.h b/src/buffers.h index 0ce940d..b827c44 100644 --- a/src/buffers.h +++ b/src/buffers.h @@ -17,7 +17,6 @@ // internal data array will be reallocate to a capacity of 'GROW_FACTOR' times // it's last capacity. - #define DECLARE_BUFFER(m_name, m_name_l, m_type) \ typedef struct { \ m_type* data; \ @@ -44,17 +43,46 @@ PKVM* vm, m_type data); \ -DECLARE_BUFFER(Uint, uint, uint32_t) -DECLARE_BUFFER(Byte, byte, uint8_t) -DECLARE_BUFFER(Var, var, Var) -DECLARE_BUFFER(String, string, String*) -DECLARE_BUFFER(Function, function, Function*) +// The buffer "template" implementation of diferent types. -// Add all the characters to the buffer, byte buffer can also be used as a -// buffer to write string (like a string stream). -void byteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str, - uint32_t length); - -#undef DECLARE_BUFFER +#define DEFINE_BUFFER(m_name, m_name_l, m_type) \ + void m_name_l##BufferInit(m_name##Buffer* self) { \ + self->data = NULL; \ + self->count = 0; \ + self->capacity = 0; \ + } \ + \ + void m_name_l##BufferClear(m_name##Buffer* self, \ + PKVM* vm) { \ + vmRealloc(vm, self->data, self->capacity * sizeof(m_type), 0); \ + self->data = NULL; \ + self->count = 0; \ + self->capacity = 0; \ + } \ + \ + void m_name_l##BufferReserve(m_name##Buffer* self, PKVM* vm, size_t size) { \ + if (self->capacity < size) { \ + int capacity = utilPowerOf2Ceil((int)size); \ + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; \ + self->data = (m_type*)vmRealloc(vm, self->data, \ + self->capacity * sizeof(m_type), capacity * sizeof(m_type)); \ + self->capacity = capacity; \ + } \ + } \ + \ + void m_name_l##BufferFill(m_name##Buffer* self, PKVM* vm, \ + m_type data, int count) { \ + \ + m_name_l##BufferReserve(self, vm, self->count + count); \ + \ + for (int i = 0; i < count; i++) { \ + self->data[self->count++] = data; \ + } \ + } \ + \ + void m_name_l##BufferWrite(m_name##Buffer* self, \ + PKVM* vm, m_type data) { \ + m_name_l##BufferFill(self, vm, data, 1); \ + } \ #endif // BUFFERS_TEMPLATE_H diff --git a/src/common.h b/src/common.h index 8287771..2838cc9 100644 --- a/src/common.h +++ b/src/common.h @@ -168,19 +168,5 @@ /* INTERNAL TYPE DEFINES */ /*****************************************************************************/ -#if VAR_NAN_TAGGING - typedef uint64_t Var; -#else - typedef struct Var Var; -#endif - -typedef struct Object Object; -typedef struct String String; -typedef struct List List; -typedef struct Map Map; -typedef struct Range Range; -typedef struct Script Script; -typedef struct Function Function; -typedef struct Fiber Fiber; #endif //PK_COMMON_H diff --git a/src/compiler.h b/src/compiler.h index 5edce88..521231a 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -9,6 +9,12 @@ #include "common.h" #include "var.h" +typedef enum { + #define OPCODE(name, _, __) OP_##name, + #include "opcodes.h" + #undef OPCODE +} Opcode; + // Pocketlang compiler is a single pass compiler, which means it doesn't go // throught the basic compilation pipline such as lexing, parsing (AST), // analyzing, intermediate codegeneration, and target codegeneration one by one diff --git a/src/core.c b/src/core.c index 2e1f8cf..0da81c9 100644 --- a/src/core.c +++ b/src/core.c @@ -195,20 +195,20 @@ void pkReturnValue(PKVM* vm, PkVar value) { // Check if a numeric value bool/number and set [value]. static inline bool isNumeric(Var var, double* value) { - if (IS_BOOL(var)) { - *value = AS_BOOL(var); - return true; - } if (IS_NUM(var)) { *value = AS_NUM(var); return true; } + if (IS_BOOL(var)) { + *value = AS_BOOL(var); + return true; + } return false; } // Check if [var] is bool/number. If not set error and return false. static inline bool validateNumeric(PKVM* vm, Var var, double* value, - const char* name) { + const char* name) { if (isNumeric(var, value)) return true; vm->fiber->error = stringFormat(vm, "$ must be a numeric value.", name); return false; @@ -216,7 +216,7 @@ static inline bool validateNumeric(PKVM* vm, Var var, double* value, // Check if [var] is integer. If not set error and return false. static inline bool validateInteger(PKVM* vm, Var var, int32_t* value, - const char* name) { + const char* name) { double number; if (isNumeric(var, &number)) { double truncated = floor(number); @@ -298,19 +298,19 @@ Script* getCoreLib(const PKVM* vm, String* name) { /* CORE BUILTIN FUNCTIONS */ /*****************************************************************************/ -#define FN_IS_PRIMITE_TYPE(name, check) \ - void coreIs##name(PKVM* vm) { \ - RET(VAR_BOOL(check(ARG1))); \ +#define FN_IS_PRIMITE_TYPE(name, check) \ + void coreIs##name(PKVM* vm) { \ + RET(VAR_BOOL(check(ARG1))); \ } -#define FN_IS_OBJ_TYPE(name, _enum) \ - void coreIs##name(PKVM* vm) { \ - Var arg1 = ARG1; \ - if (IS_OBJ_TYPE(arg1, _enum)) { \ - RET(VAR_TRUE); \ - } else { \ - RET(VAR_FALSE); \ - } \ +#define FN_IS_OBJ_TYPE(name, _enum) \ + void coreIs##name(PKVM* vm) { \ + Var arg1 = ARG1; \ + if (IS_OBJ_TYPE(arg1, _enum)) { \ + RET(VAR_TRUE); \ + } else { \ + RET(VAR_FALSE); \ + } \ } FN_IS_PRIMITE_TYPE(Null, IS_NULL) diff --git a/src/core.h b/src/core.h index dbc6b2c..551d71f 100644 --- a/src/core.h +++ b/src/core.h @@ -6,8 +6,8 @@ #ifndef CORE_H #define CORE_H -#include "var.h" #include "common.h" +#include "var.h" // Initialize core language, builtin function and core libs. void initializeCore(PKVM* vm); diff --git a/src/debug.h b/src/debug.h index 728e150..7ef0619 100644 --- a/src/debug.h +++ b/src/debug.h @@ -7,6 +7,7 @@ #define DEBUG_H #include "common.h" +#include "var.h" // Dump the value of the [value] without a new line at the end. void dumpValue(PKVM* vm, Var value); diff --git a/src/var.c b/src/var.c index 2af14fc..d799b2b 100644 --- a/src/var.c +++ b/src/var.c @@ -75,6 +75,21 @@ const char* pkStringGetData(const PkVar value) { // capacity by the GROW_FACTOR. #define GROW_FACTOR 2 +// Buffer implementations. +DEFINE_BUFFER(Uint, uint, uint32_t) +DEFINE_BUFFER(Byte, byte, uint8_t) +DEFINE_BUFFER(Var, var, Var) +DEFINE_BUFFER(String, string, String*) +DEFINE_BUFFER(Function, function, Function*) + +void byteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str, + uint32_t length) { + byteBufferReserve(self, vm, self->count + length); + for (uint32_t i = 0; i < length; i++) { + self->data[self->count++] = *(str++); + } +} + void varInitObject(Object* self, PKVM* vm, ObjectType type) { self->type = type; self->is_marked = false; @@ -122,7 +137,6 @@ void grayVarBuffer(PKVM* vm, VarBuffer* self) { GRAY_OBJ_BUFFER(String) GRAY_OBJ_BUFFER(Function) - static void blackenObject(Object* obj, PKVM* vm) { // TODO: trace here. @@ -1012,7 +1026,8 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, case OBJ_FIBER: { const Fiber* fb = (const Fiber*)obj; byteBufferAddString(buff, vm, "[Fiber:", 7); - byteBufferAddString(buff, vm, fb->func->name, strlen(fb->func->name)); + byteBufferAddString(buff, vm, fb->func->name, + (uint32_t)strlen(fb->func->name)); byteBufferWrite(buff, vm, ']'); return; } diff --git a/src/var.h b/src/var.h index 21cd22f..8d380e9 100644 --- a/src/var.h +++ b/src/var.h @@ -6,6 +6,9 @@ #ifndef VAR_H #define VAR_H +#include "buffers.h" +#include "common.h" + /** @file * A simple dynamic type system library for small dynamic typed languages using * a technique called NaN-tagging (optional). The method is inspired from the @@ -20,9 +23,6 @@ * programme inefficient for small types such null, bool, int and float. */ -#include "common.h" -#include "buffers.h" - // To use dynamic variably-sized struct with a tail array add an array at the // end of the struct with size \ref DYNAMIC_TAIL_ARRAY. This method was a // legacy standard called "struct hack". @@ -36,6 +36,14 @@ // Number of maximum import statements in a script. #define MAX_IMPORT_SCRIPTS 16 +// There are 2 main implemenation of Var's internal representation. First one +// is NaN-tagging, and the second one is union-tagging. (read below for more). +#if VAR_NAN_TAGGING + typedef uint64_t Var; +#else + typedef struct Var Var; +#endif + /** * The IEEE 754 double precision float bit representation. * @@ -160,7 +168,7 @@ typedef enum { VAR_OBJECT, //< Base type for all \ref var_Object types. } VarType; -typedef struct { +struct Var { VarType type; union { bool _bool; @@ -168,10 +176,33 @@ typedef struct { double _float; Object* _obj; }; -} var; +}; #endif // VAR_NAN_TAGGING +// Type definition of pocketlang heap allocated types. +typedef struct Object Object; +typedef struct String String; +typedef struct List List; +typedef struct Map Map; +typedef struct Range Range; +typedef struct Script Script; +typedef struct Function Function; +typedef struct Fiber Fiber; + +// Declaration of buffer objects of different types. +DECLARE_BUFFER(Uint, uint, uint32_t) +DECLARE_BUFFER(Byte, byte, uint8_t) +DECLARE_BUFFER(Var, var, Var) +DECLARE_BUFFER(String, string, String*) +DECLARE_BUFFER(Function, function, Function*) + +// Add all the characters to the buffer, byte buffer can also be used as a +// buffer to write string (like a string stream). +void byteBufferAddString(ByteBuffer* self, PKVM* vm, const char* str, + uint32_t length); + + typedef enum { OBJ_STRING, OBJ_LIST, diff --git a/src/vm.h b/src/vm.h index 53239f2..f31c7a4 100644 --- a/src/vm.h +++ b/src/vm.h @@ -35,12 +35,6 @@ // allocated so far plus the fill factor of it. #define HEAP_FILL_PERCENT 75 -typedef enum { - #define OPCODE(name, _, __) OP_##name, - #include "opcodes.h" - #undef OPCODE -} Opcode; - // Builtin functions are stored in an array in the VM (unlike script functions // they're member of function buffer of the script) and this struct is a single // entry of the array.