mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +08:00
Merge pull request #42 from ThakeeNathees/fiber-docs
fiber documentations
This commit is contained in:
commit
3092118008
@ -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.
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
14
src/common.h
14
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
|
||||
|
@ -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
|
||||
|
34
src/core.c
34
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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
19
src/var.c
19
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;
|
||||
}
|
||||
|
41
src/var.h
41
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,
|
||||
|
6
src/vm.h
6
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.
|
||||
|
Loading…
Reference in New Issue
Block a user