Merge pull request #42 from ThakeeNathees/fiber-docs

fiber documentations
This commit is contained in:
Thakee Nathees 2021-06-06 19:54:13 +05:30
commit 3092118008
14 changed files with 222 additions and 131 deletions

View File

@ -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.

View File

@ -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')
```
# 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)
```

View File

@ -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)
```

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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,

View File

@ -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.