mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
iterator implemented
This commit is contained in:
parent
2b132409e2
commit
38a22fea9f
24
README.md
24
README.md
@ -6,29 +6,29 @@ MiniScript is a simple embeddable, functional, dynamic-typed, bytecode-interpret
|
||||
|
||||
```ruby
|
||||
|
||||
## Find and return the maximum value in the array.
|
||||
def get_max(arr)
|
||||
ret = arr[0]
|
||||
for i in 1..arr.length
|
||||
ret = max(ret, arr[i])
|
||||
## Find and return the maximum value in the [list].
|
||||
def get_max(list)
|
||||
ret = list[0]
|
||||
for i in 1..list.length
|
||||
ret = max(ret, list[i])
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
## Return an array where each element returns true with function [fn] and
|
||||
## belongs to [arr].
|
||||
def filter(arr, fn)
|
||||
## Return a list where each element returns true with function [fn] and
|
||||
## belongs to [list].
|
||||
def filter(list, fn)
|
||||
ret = []
|
||||
for elem in arr
|
||||
for elem in list
|
||||
if fn(elem)
|
||||
array_append(ret, elem)
|
||||
list_append(ret, elem)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
array = [42, null, 3.14, "String", 0..10, [100]]
|
||||
nums = filter(array, is_num)
|
||||
list = [42, null, 3.14, "String", 0..10, ['hello']]
|
||||
nums = filter(list, is_num)
|
||||
print(get_max(nums))
|
||||
|
||||
```
|
||||
|
@ -63,11 +63,10 @@ typedef struct Var Var;
|
||||
|
||||
typedef struct Object Object;
|
||||
typedef struct String String;
|
||||
typedef struct Array Array;
|
||||
typedef struct List List;
|
||||
typedef struct Range Range;
|
||||
|
||||
typedef struct Script Script;
|
||||
//typedef struct Class Class;
|
||||
typedef struct Function Function;
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -115,6 +114,7 @@ typedef struct Function Function;
|
||||
#endif // DEBUG
|
||||
|
||||
#define TODO ASSERT(false, "TODO")
|
||||
#define OOPS "Oops a bug!! report plese."
|
||||
|
||||
// Allocate object of [type] using the vmRealloc function.
|
||||
#define ALLOCATE(vm, type) \
|
||||
|
140
src/compiler.c
140
src/compiler.c
@ -332,14 +332,14 @@ static void setNextToken(Parser* parser, TokenType type);
|
||||
static bool matchChar(Parser* parser, char c);
|
||||
static bool matchLine(Parser* parser);
|
||||
|
||||
static void eatString(Parser* parser) {
|
||||
static void eatString(Parser* parser, char quote) {
|
||||
ByteBuffer buff;
|
||||
byteBufferInit(&buff);
|
||||
|
||||
while (true) {
|
||||
char c = eatChar(parser);
|
||||
|
||||
if (c == '"') break;
|
||||
if (c == quote) break;
|
||||
|
||||
if (c == '\0') {
|
||||
lexError(parser, "Non terminated string.");
|
||||
@ -352,6 +352,7 @@ static void eatString(Parser* parser) {
|
||||
if (c == '\\') {
|
||||
switch (eatChar(parser)) {
|
||||
case '"': byteBufferWrite(&buff, parser->vm, '"'); break;
|
||||
case '\'': byteBufferWrite(&buff, parser->vm, '\''); break;
|
||||
case '\\': byteBufferWrite(&buff, parser->vm, '\\'); break;
|
||||
case 'n': byteBufferWrite(&buff, parser->vm, '\n'); break;
|
||||
case 'r': byteBufferWrite(&buff, parser->vm, '\r'); break;
|
||||
@ -427,7 +428,8 @@ static void eatNumber(Parser* parser) {
|
||||
while (utilIsDigit(peekChar(parser)))
|
||||
eatChar(parser);
|
||||
|
||||
if (matchChar(parser, '.')) {
|
||||
if (peekChar(parser) == '.' && utilIsDigit(peekNextChar(parser))) {
|
||||
matchChar(parser, '.');
|
||||
while (utilIsDigit(peekChar(parser)))
|
||||
eatChar(parser);
|
||||
}
|
||||
@ -574,7 +576,9 @@ static void lexToken(Parser* parser) {
|
||||
setNextTwoCharToken(parser, '=', TK_FSLASH, TK_DIVEQ);
|
||||
return;
|
||||
|
||||
case '"': eatString(parser); return;
|
||||
case '"': eatString(parser, '"'); return;
|
||||
|
||||
case '\'': eatString(parser, '\''); return;
|
||||
|
||||
default: {
|
||||
|
||||
@ -731,14 +735,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
||||
|
||||
NameSearchResult result;
|
||||
result.type = NAME_NOT_DEFINED;
|
||||
|
||||
// Search through builtin functions.
|
||||
int index = findBuiltinFunction(name, length);
|
||||
if (index != -1) {
|
||||
result.type = NAME_BUILTIN;
|
||||
result.index = index;
|
||||
return result;
|
||||
}
|
||||
int index;
|
||||
|
||||
// Search through local and global valriables.
|
||||
NameDefnType type = NAME_LOCAL_VAR; //< Will change to local.
|
||||
@ -771,6 +768,14 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
||||
return result;
|
||||
}
|
||||
|
||||
// Search through builtin functions.
|
||||
index = findBuiltinFunction(name, length);
|
||||
if (index != -1) {
|
||||
result.type = NAME_BUILTIN;
|
||||
result.index = index;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -799,7 +804,7 @@ static void exprBinaryOp(Compiler* compiler, bool can_assign);
|
||||
static void exprUnaryOp(Compiler* compiler, bool can_assign);
|
||||
|
||||
static void exprGrouping(Compiler* compiler, bool can_assign);
|
||||
static void exprArray(Compiler* compiler, bool can_assign);
|
||||
static void exprList(Compiler* compiler, bool can_assign);
|
||||
static void exprMap(Compiler* compiler, bool can_assign);
|
||||
|
||||
static void exprCall(Compiler* compiler, bool can_assign);
|
||||
@ -824,7 +829,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
|
||||
/* TK_HASH */ NO_RULE,
|
||||
/* TK_LPARAN */ { exprGrouping, exprCall, PREC_CALL },
|
||||
/* TK_RPARAN */ NO_RULE,
|
||||
/* TK_LBRACKET */ { exprArray, exprSubscript, PREC_SUBSCRIPT },
|
||||
/* TK_LBRACKET */ { exprList, exprSubscript, PREC_SUBSCRIPT },
|
||||
/* TK_RBRACKET */ NO_RULE,
|
||||
/* TK_LBRACE */ { exprMap, NULL, NO_INFIX },
|
||||
/* TK_RBRACE */ NO_RULE,
|
||||
@ -1026,7 +1031,28 @@ static void exprGrouping(Compiler* compiler, bool can_assign) {
|
||||
consume(&compiler->parser, TK_RPARAN, "Expected ')' after expression ");
|
||||
}
|
||||
|
||||
static void exprArray(Compiler* compiler, bool can_assign) { TODO; }
|
||||
static void exprList(Compiler* compiler, bool can_assign) {
|
||||
|
||||
emitOpcode(compiler, OP_PUSH_LIST);
|
||||
int size_index = emitShort(compiler, 0);
|
||||
|
||||
int size = 0;
|
||||
do {
|
||||
skipNewLines(&compiler->parser);
|
||||
if (peek(&compiler->parser) == TK_COMMA) break;
|
||||
|
||||
compileExpression(compiler);
|
||||
emitOpcode(compiler, OP_LIST_APPEND);
|
||||
size++;
|
||||
|
||||
} while (match(&compiler->parser, TK_COMMA));
|
||||
|
||||
consume(&compiler->parser, TK_RBRACKET, "Expected ']' after list elements.");
|
||||
|
||||
compiler->function->fn->opcodes.data[size_index] = (size >> 8) & 0xff;
|
||||
compiler->function->fn->opcodes.data[size_index + 1] = size & 0xff;
|
||||
}
|
||||
|
||||
static void exprMap(Compiler* compiler, bool can_assign) { TODO; }
|
||||
|
||||
static void exprCall(Compiler* compiler, bool can_assign) {
|
||||
@ -1211,14 +1237,23 @@ static void emitConstant(Compiler* compiler, Var value) {
|
||||
emitShort(compiler, index);
|
||||
}
|
||||
|
||||
// Update the jump offset.
|
||||
static void patchJump(Compiler* compiler, int addr_index) {
|
||||
int jump_to = (int)compiler->function->fn->opcodes.count;
|
||||
ASSERT(jump_to < MAX_JUMP, "Too large address to jump.");
|
||||
int jump_to = (int)compiler->function->fn->opcodes.count - addr_index - 2;
|
||||
ASSERT(jump_to < MAX_JUMP, "Too large address offset to jump to.");
|
||||
|
||||
compiler->function->fn->opcodes.data[addr_index] = (jump_to >> 8) & 0xff;
|
||||
compiler->function->fn->opcodes.data[addr_index + 1] = jump_to & 0xff;
|
||||
}
|
||||
|
||||
// Jump back to the start of the loop.
|
||||
static void emitLoopJump(Compiler* compiler) {
|
||||
emitOpcode(compiler, OP_LOOP);
|
||||
int offset = (int)compiler->function->fn->opcodes.count -
|
||||
compiler->loop->start + 2;
|
||||
emitShort(compiler, offset);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* COMPILING (PARSE TOPLEVEL) *
|
||||
****************************************************************************/
|
||||
@ -1229,14 +1264,12 @@ static void compileBlockBody(Compiler* compiler, bool if_body);
|
||||
static void compileFunction(Compiler* compiler, bool is_native) {
|
||||
|
||||
Parser* parser = &compiler->parser;
|
||||
|
||||
consume(&compiler->parser, TK_NAME, "Expected a function name.");
|
||||
consume(parser, TK_NAME, "Expected a function name.");
|
||||
|
||||
const char* name_start = parser->previous.start;
|
||||
int name_length = parser->previous.length;
|
||||
NameSearchResult result = compilerSearchName(compiler, name_start,
|
||||
name_length);
|
||||
|
||||
if (result.type != NAME_NOT_DEFINED) {
|
||||
parseError(&compiler->parser, "Name %.*s already exists.", name_length,
|
||||
name_start);
|
||||
@ -1309,6 +1342,8 @@ static void compileFunction(Compiler* compiler, bool is_native) {
|
||||
|
||||
// Finish a block body.
|
||||
static void compileBlockBody(Compiler* compiler, bool if_body) {
|
||||
|
||||
consumeStartBlock(&compiler->parser);
|
||||
compilerEnterBlock(compiler);
|
||||
skipNewLines(&compiler->parser);
|
||||
|
||||
@ -1338,8 +1373,6 @@ static void compileIfStatement(Compiler* compiler) {
|
||||
emitOpcode(compiler, OP_JUMP_IF_NOT);
|
||||
int ifpatch = emitShort(compiler, 0xffff); //< Will be patched.
|
||||
|
||||
consumeStartBlock(&compiler->parser);
|
||||
|
||||
compileBlockBody(compiler, true);
|
||||
|
||||
if (match(&compiler->parser, TK_ELIF)) {
|
||||
@ -1365,30 +1398,78 @@ static void compileWhileStatement(Compiler* compiler) {
|
||||
loop.outer_loop = compiler->loop;
|
||||
compiler->loop = &loop;
|
||||
|
||||
skipNewLines(&compiler->parser);
|
||||
compileExpression(compiler); //< Condition.
|
||||
emitOpcode(compiler, OP_JUMP_IF_NOT);
|
||||
int whilepatch = emitByte(compiler, 0xffff); //< Will be patched.
|
||||
|
||||
compileBlockBody(compiler, false);
|
||||
|
||||
emitOpcode(compiler, OP_JUMP);
|
||||
emitShort(compiler, loop.start);
|
||||
|
||||
emitLoopJump(compiler);
|
||||
patchJump(compiler, whilepatch);
|
||||
|
||||
// Patch break statement.
|
||||
for (int i = 0; i < compiler->loop->patch_count; i++) {
|
||||
patchJump(compiler, compiler->loop->patches[i]);
|
||||
}
|
||||
|
||||
compiler->loop = loop.outer_loop;
|
||||
|
||||
skipNewLines(&compiler->parser);
|
||||
consume(&compiler->parser, TK_END, "Expected 'end' after statement end.");
|
||||
}
|
||||
|
||||
static void compileForStatement(Compiler* compiler) {
|
||||
TODO;
|
||||
compilerEnterBlock(compiler);
|
||||
|
||||
Parser* parser = &compiler->parser;
|
||||
consume(parser, TK_NAME, "Expected an iterator name.");
|
||||
|
||||
// Unlike functions local variable could shadow a name.
|
||||
const char* iter_name = parser->previous.start;
|
||||
int iter_len = parser->previous.length;
|
||||
int iter_line = parser->previous.line;
|
||||
|
||||
consume(parser, TK_IN, "Expected 'in' after iterator name.");
|
||||
|
||||
// Compile and store container.
|
||||
int container = compilerAddVariable(compiler, "@container", 10, iter_line);
|
||||
compileExpression(compiler);
|
||||
|
||||
// Add iterator to locals. It would initially be null and once the loop
|
||||
// started it'll be an increasing integer indicating that the current
|
||||
// loop is nth.
|
||||
int iterator = compilerAddVariable(compiler, "@iterator", 9, iter_line);
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
|
||||
// Add the iteration value. It'll be updated to each element in an array of
|
||||
// each character in a string etc.
|
||||
int iter_value = compilerAddVariable(compiler, iter_name, iter_len,
|
||||
iter_line);
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
|
||||
Loop loop;
|
||||
loop.start = (int)compiler->function->fn->opcodes.count;
|
||||
loop.patch_count = 0;
|
||||
loop.outer_loop = compiler->loop;
|
||||
compiler->loop = &loop;
|
||||
|
||||
// Compile next iteration.
|
||||
emitOpcode(compiler, OP_ITER);
|
||||
int forpatch = emitShort(compiler, 0xffff);
|
||||
|
||||
compileBlockBody(compiler, false);
|
||||
|
||||
emitLoopJump(compiler);
|
||||
patchJump(compiler, forpatch);
|
||||
|
||||
// Patch break statement.
|
||||
for (int i = 0; i < compiler->loop->patch_count; i++) {
|
||||
patchJump(compiler, compiler->loop->patches[i]);
|
||||
}
|
||||
compiler->loop = loop.outer_loop;
|
||||
|
||||
skipNewLines(&compiler->parser);
|
||||
consume(&compiler->parser, TK_END, "Expected 'end' after statement end.");
|
||||
compilerExitBlock(compiler); //< Iterator scope.
|
||||
}
|
||||
|
||||
// Compiles a statement. Assignment could be an assignment statement or a new
|
||||
@ -1417,8 +1498,7 @@ static void compileStatement(Compiler* compiler) {
|
||||
}
|
||||
|
||||
consumeEndStatement(parser);
|
||||
emitOpcode(compiler, OP_JUMP);
|
||||
emitShort(compiler, compiler->loop->start);
|
||||
emitLoopJump(compiler);
|
||||
|
||||
} else if (match(parser, TK_RETURN)) {
|
||||
|
||||
|
193
src/core.c
193
src/core.c
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <math.h>
|
||||
#include "vm.h"
|
||||
|
||||
typedef struct {
|
||||
@ -15,6 +17,9 @@ typedef struct {
|
||||
// Count of builtin function +1 for termination.
|
||||
#define BUILTIN_COUNT 50
|
||||
|
||||
// Convert number var as int32_t. Check if it's number before using it.
|
||||
#define _AS_INTEGER(var) (int32_t)trunc(AS_NUM(var))
|
||||
|
||||
// Array of all builtin functions.
|
||||
_BuiltinFn builtins[BUILTIN_COUNT];
|
||||
|
||||
@ -42,6 +47,65 @@ int findBuiltinFunction(const char* name, int length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validators /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Check if a numeric value bool/number and set [value].
|
||||
static 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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if [var] is bool/number. If not set error and return false.
|
||||
static bool validateNumeric(MSVM* vm, Var var, double* value,
|
||||
const char* name) {
|
||||
if (isNumeric(var, value)) return true;
|
||||
msSetRuntimeError(vm, "%s must be a numeric value.", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if [var] is integer. If not set error and return false.
|
||||
static bool validateIngeger(MSVM* vm, Var var, int32_t* value,
|
||||
const char* name) {
|
||||
double number;
|
||||
if (isNumeric(var, &number)) {
|
||||
double truncated = trunc(number);
|
||||
if (truncated == number) {
|
||||
*value = (int32_t)(truncated);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
msSetRuntimeError(vm, "%s must be an integer.", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool validateIndex(MSVM* vm, int32_t index, int32_t size,
|
||||
const char* container) {
|
||||
if (index < 0 || size <= index) {
|
||||
msSetRuntimeError(vm, "%s index out of range.", container);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Builtin Functions //////////////////////////////////////////////////////////
|
||||
|
||||
// Argument getter (1 based).
|
||||
#define ARG(n) vm->rbp[n]
|
||||
|
||||
// Argument count used in variadic functions.
|
||||
#define ARGC ((int)(vm->sp - vm->rbp) - 1)
|
||||
|
||||
// Set return value.
|
||||
#define RET(value) vm->rbp[0] = value
|
||||
|
||||
Function* getBuiltinFunction(int index) {
|
||||
ASSERT(index < BUILTIN_COUNT, "Index out of bound.");
|
||||
return &builtins[index].fn;
|
||||
@ -49,16 +113,16 @@ Function* getBuiltinFunction(int index) {
|
||||
|
||||
#define FN_IS_PRIMITE_TYPE(name, check) \
|
||||
void coreIs##name(MSVM* vm) { \
|
||||
vm->rbp[0] = VAR_BOOL(check(vm->rbp[1])); \
|
||||
RET(VAR_BOOL(check(ARG(1)))); \
|
||||
}
|
||||
|
||||
#define FN_IS_OBJ_TYPE(name, _enum) \
|
||||
void coreIs##name(MSVM* vm) { \
|
||||
Var arg1 = vm->rbp[1]; \
|
||||
Var arg1 = ARG(1); \
|
||||
if (IS_OBJ(arg1) && AS_OBJ(arg1)->type == _enum) { \
|
||||
vm->rbp[0] = VAR_TRUE; \
|
||||
RET(VAR_TRUE); \
|
||||
} else { \
|
||||
vm->rbp[0] = VAR_FALSE; \
|
||||
RET(VAR_FALSE); \
|
||||
} \
|
||||
}
|
||||
|
||||
@ -67,7 +131,7 @@ FN_IS_PRIMITE_TYPE(Bool, IS_BOOL)
|
||||
FN_IS_PRIMITE_TYPE(Num, IS_NUM)
|
||||
|
||||
FN_IS_OBJ_TYPE(String, OBJ_STRING)
|
||||
FN_IS_OBJ_TYPE(Array, OBJ_ARRAY)
|
||||
FN_IS_OBJ_TYPE(List, OBJ_LIST)
|
||||
FN_IS_OBJ_TYPE(Map, OBJ_MAP)
|
||||
FN_IS_OBJ_TYPE(Range, OBJ_RANGE)
|
||||
FN_IS_OBJ_TYPE(Function, OBJ_FUNC)
|
||||
@ -75,21 +139,25 @@ FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT)
|
||||
FN_IS_OBJ_TYPE(UserObj, OBJ_USER)
|
||||
|
||||
void coreToString(MSVM* vm) {
|
||||
Var arg1 = vm->rbp[1];
|
||||
vm->rbp[0] = VAR_OBJ(&toString(vm, arg1)->_super);
|
||||
RET(VAR_OBJ(&toString(vm, ARG(1), false)->_super));
|
||||
}
|
||||
|
||||
void corePrint(MSVM* vm) {
|
||||
Var arg1 = vm->rbp[1];
|
||||
String* str; //< Will be cleaned by garbage collector;
|
||||
|
||||
for (int i = 1; i <= ARGC; i++) {
|
||||
Var arg = ARG(i);
|
||||
// If it's already a string don't allocate a new string instead use it.
|
||||
if (IS_OBJ(arg1) && AS_OBJ(arg1)->type == OBJ_STRING) {
|
||||
str = (String*)AS_OBJ(arg1);
|
||||
if (IS_OBJ(arg) && AS_OBJ(arg)->type == OBJ_STRING) {
|
||||
str = (String*)AS_OBJ(arg);
|
||||
} else {
|
||||
str = toString(vm, arg1);
|
||||
str = toString(vm, arg, false);
|
||||
}
|
||||
|
||||
if (i != 1) vm->config.write_fn(vm, " ");
|
||||
vm->config.write_fn(vm, str->data);
|
||||
}
|
||||
|
||||
vm->config.write_fn(vm, "\n");
|
||||
}
|
||||
|
||||
@ -112,7 +180,7 @@ void initializeCore(MSVM* vm) {
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_num", 1, coreIsNum);
|
||||
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_string", 1, coreIsString);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_array", 1, coreIsArray);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_list", 1, coreIsList);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_map", 1, coreIsMap);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_range", 1, coreIsRange);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_function", 1, coreIsFunction);
|
||||
@ -120,7 +188,7 @@ void initializeCore(MSVM* vm) {
|
||||
initializeBuiltinFN(vm, &builtins[i++], "is_userobj", 1, coreIsUserObj);
|
||||
|
||||
initializeBuiltinFN(vm, &builtins[i++], "to_string", 1, coreToString);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "print", 1, corePrint);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "print", -1, corePrint);
|
||||
initializeBuiltinFN(vm, &builtins[i++], "import", 1, coreImport);
|
||||
|
||||
// Sentinal to mark the end of the array.
|
||||
@ -128,28 +196,6 @@ void initializeCore(MSVM* vm) {
|
||||
|
||||
}
|
||||
|
||||
// Validators /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Check if a numeric value bool/number and set [value].
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if [var] is bool/number. if not set error and return false.
|
||||
bool validateNumeric(MSVM* vm, Var var, double* value, const char* arg) {
|
||||
if (isNumeric(var, value)) return true;
|
||||
msSetRuntimeError(vm, "%s must be a numeric value.", arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operators //////////////////////////////////////////////////////////////////
|
||||
|
||||
Var varAdd(MSVM* vm, Var v1, Var v2) {
|
||||
@ -191,6 +237,83 @@ Var varDivide(MSVM* vm, Var v1, Var v2) {
|
||||
}
|
||||
|
||||
bool varIterate(MSVM* vm, Var seq, Var* iterator, Var* value) {
|
||||
|
||||
#ifdef DEBUG
|
||||
int32_t _temp;
|
||||
ASSERT(IS_NUM(*iterator) || IS_NULL(*iterator), OOPS);
|
||||
if (IS_NUM(*iterator)) {
|
||||
ASSERT(validateIngeger(vm, *iterator, &_temp, "Assetion."), OOPS);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Primitive types are not iterable.
|
||||
if (!IS_OBJ(seq)) {
|
||||
if (IS_NULL(seq)) {
|
||||
msSetRuntimeError(vm, "Null is not iterable.");
|
||||
} else if (IS_BOOL(seq)) {
|
||||
msSetRuntimeError(vm, "Boolenan is not iterable.");
|
||||
} else if (IS_NUM(seq)) {
|
||||
msSetRuntimeError(vm, "Number is not iterable.");
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
*value = VAR_NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
Object* obj = AS_OBJ(seq);
|
||||
|
||||
int32_t iter = 0; //< Nth iteration.
|
||||
if (IS_NUM(*iterator)) {
|
||||
iter = _AS_INTEGER(*iterator);
|
||||
}
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_STRING: {
|
||||
TODO; // Need to consider utf8.
|
||||
|
||||
TODO; // Return string[index].
|
||||
}
|
||||
|
||||
case OBJ_LIST: {
|
||||
VarBuffer* elems = &((List*)obj)->elements;
|
||||
if (iter < 0 || iter >= elems->count) {
|
||||
return false; //< Stop iteration.
|
||||
}
|
||||
*value = elems->data[iter];
|
||||
*iterator = VAR_NUM((double)iter + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
case OBJ_MAP:
|
||||
TODO;
|
||||
|
||||
case OBJ_RANGE: {
|
||||
double from = ((Range*)obj)->from;
|
||||
double to = ((Range*)obj)->to;
|
||||
if (from == to) return false;
|
||||
|
||||
double current;
|
||||
if (from <= to) { //< Straight range.
|
||||
current = from + (double)iter;
|
||||
} else { //< Reversed range.
|
||||
current = from - (double)iter;
|
||||
}
|
||||
if (current == to) return false;
|
||||
*value = VAR_NUM(current);
|
||||
*iterator = VAR_NUM((double)iter + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
case OBJ_SCRIPT:
|
||||
case OBJ_FUNC:
|
||||
case OBJ_USER:
|
||||
TODO;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
TODO;
|
||||
return false;
|
||||
}
|
@ -18,16 +18,24 @@
|
||||
OPCODE(CONSTANT, 2, 1)
|
||||
|
||||
// Push null on the stack.
|
||||
OPCODE(PUSH_NULL, 0, 0)
|
||||
OPCODE(PUSH_NULL, 0, 1)
|
||||
|
||||
// Push self on the stack. If the runtime don't have self it'll push null.
|
||||
OPCODE(PUSH_SELF, 0, 0)
|
||||
OPCODE(PUSH_SELF, 0, 1)
|
||||
|
||||
// Push true on the stack.
|
||||
OPCODE(PUSH_TRUE, 0, 0)
|
||||
OPCODE(PUSH_TRUE, 0, 1)
|
||||
|
||||
// Push false on the stack.
|
||||
OPCODE(PUSH_FALSE, 0, 0)
|
||||
OPCODE(PUSH_FALSE, 0, 1)
|
||||
|
||||
// Push a new list to construct from literal.
|
||||
// param: 2 bytes list size (defalt is 0).
|
||||
OPCODE(PUSH_LIST, 2, 1)
|
||||
|
||||
// Pop the value on the stack the next stack top would be a list. Append the
|
||||
// value to the list. Used in literal array construction.
|
||||
OPCODE(LIST_APPEND, 0, -1)
|
||||
|
||||
// Push stack local on top of the stack. Locals at 0 to 8 marked explicitly
|
||||
// since it's performance criticle.
|
||||
@ -94,14 +102,23 @@ OPCODE(POP, 0, -1)
|
||||
//OPCODE(CALL_8, 2, -8)
|
||||
OPCODE(CALL, 4, -0) //< Will calculated at compile time.
|
||||
|
||||
// The address to jump to. It'll set the ip to the address it should jump to
|
||||
// and the address is absolute not an offset from ip's current value.
|
||||
// param: 2 bytes jump address.
|
||||
// The stack top will be iteration value, next one is iterator (integer) and
|
||||
// next would be the container. It'll update those values but not push or pop
|
||||
// any values. We need to ensure that stack state at the point.
|
||||
// param: 2 bytes jump offset if the iteration should stop.
|
||||
OPCODE(ITER, 2, 0)
|
||||
|
||||
// The address offset to jump to. It'll add the offset to ip.
|
||||
// param: 2 bytes jump address offset.
|
||||
OPCODE(JUMP, 2, 0)
|
||||
|
||||
// The address offset to jump to. It'll SUBTRACT the offset to ip.
|
||||
// param: 2 bytes jump address offset.
|
||||
OPCODE(LOOP, 2, 0)
|
||||
|
||||
// Pop the stack top value and if it's true jump.
|
||||
// param: 2 bytes jump address.
|
||||
OPCODE(JUMP_NOT, 2, -1)
|
||||
OPCODE(JUMP_IF, 2, -1)
|
||||
|
||||
// Pop the stack top value and if it's false jump.
|
||||
// param: 2 bytes jump address.
|
||||
|
21
src/var.c
21
src/var.c
@ -58,6 +58,16 @@ String* newString(MSVM* vm, const char* text, uint32_t length) {
|
||||
return string;
|
||||
}
|
||||
|
||||
List* newList(MSVM* vm, uint32_t size) {
|
||||
List* list = ALLOCATE(vm, List);
|
||||
varInitObject(&list->_super, vm, OBJ_LIST);
|
||||
varBufferInit(&list->elements);
|
||||
if (size > 0) {
|
||||
varBufferFill(&list->elements, vm, VAR_NULL, size);
|
||||
list->elements.count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Range* newRange(MSVM* vm, double from, double to) {
|
||||
Range* range = ALLOCATE(vm, Range);
|
||||
varInitObject(&range->_super, vm, OBJ_RANGE);
|
||||
@ -95,7 +105,7 @@ Function* newFunction(MSVM* vm, const char* name, Script* owner,
|
||||
|
||||
func->name = name;
|
||||
func->owner = owner;
|
||||
func->arity = -1;
|
||||
func->arity = -2; // -1 means variadic args.
|
||||
|
||||
func->is_native = is_native;
|
||||
|
||||
@ -125,7 +135,7 @@ bool isVauesSame(Var v1, Var v2) {
|
||||
#endif
|
||||
}
|
||||
|
||||
String* toString(MSVM* vm, Var v) {
|
||||
String* toString(MSVM* vm, Var v, bool recursive) {
|
||||
|
||||
if (IS_NULL(v)) {
|
||||
return newString(vm, "null", 4);
|
||||
@ -147,10 +157,15 @@ String* toString(MSVM* vm, Var v) {
|
||||
Object* obj = AS_OBJ(v);
|
||||
switch (obj->type) {
|
||||
case OBJ_STRING:
|
||||
{
|
||||
// If recursive return with quotes (ex: [42, "hello", 0..10])
|
||||
if (!recursive)
|
||||
return newString(vm, ((String*)obj)->data, ((String*)obj)->length);
|
||||
TODO; //< Add quotes around the string.
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJ_ARRAY: return newString(vm, "[Array]", 7); // TODO;
|
||||
case OBJ_LIST: return newString(vm, "[Array]", 7); // TODO;
|
||||
case OBJ_MAP: return newString(vm, "[Map]", 5); // TODO;
|
||||
case OBJ_RANGE: return newString(vm, "[Range]", 7); // TODO;
|
||||
case OBJ_SCRIPT: return newString(vm, "[Script]", 8); // TODO;
|
||||
|
14
src/var.h
14
src/var.h
@ -153,7 +153,7 @@
|
||||
|
||||
#define AS_STRING(value) ((String*)AS_OBJ(value))
|
||||
#define AS_CSTRING(value) (AS_STRING(value)->data)
|
||||
#define AS_ARRAY(value) ((Array*)AS_OBJ(value))
|
||||
#define AS_ARRAY(value) ((List*)AS_OBJ(value))
|
||||
#define AS_MAP(value) ((Map*)AS_OBJ(value))
|
||||
#define AS_RANGE(value) ((Range*)AS_OBJ(value))
|
||||
|
||||
@ -189,7 +189,7 @@ typedef struct {
|
||||
|
||||
typedef enum /* ObjectType */ {
|
||||
OBJ_STRING,
|
||||
OBJ_ARRAY,
|
||||
OBJ_LIST,
|
||||
OBJ_MAP,
|
||||
OBJ_RANGE,
|
||||
|
||||
@ -215,7 +215,7 @@ struct String {
|
||||
char data[DYNAMIC_TAIL_ARRAY];
|
||||
};
|
||||
|
||||
struct Array {
|
||||
struct List {
|
||||
Object _super;
|
||||
|
||||
VarBuffer elements; //< Elements of the array.
|
||||
@ -280,6 +280,9 @@ double varToDouble(Var value);
|
||||
// Allocate new String object and return String*.
|
||||
String* newString(MSVM* vm, const char* text, uint32_t length);
|
||||
|
||||
// Allocate new List and return List*.
|
||||
List* newList(MSVM* vm, uint32_t size);
|
||||
|
||||
// Allocate new Range object and return Range*.
|
||||
Range* newRange(MSVM* vm, double from, double to);
|
||||
|
||||
@ -296,7 +299,8 @@ Function* newFunction(MSVM* vm, const char* name, Script* owner,
|
||||
// Returns true if both variables are the same.
|
||||
bool isVauesSame(Var v1, Var v2);
|
||||
|
||||
// Returns the string version of the value.
|
||||
String* toString(MSVM* vm, Var v);
|
||||
// Returns the string version of the value. Note: pass false as [_recursive]
|
||||
// It's an internal use (or may be I could make a wrapper around).
|
||||
String* toString(MSVM* vm, Var v, bool _recursive);
|
||||
|
||||
#endif // VAR_H
|
||||
|
76
src/vm.c
76
src/vm.c
@ -61,7 +61,7 @@ void vmPopTempRef(MSVM* self) {
|
||||
void _printStackTop(MSVM* vm) {
|
||||
if (vm->sp != vm->stack) {
|
||||
Var v = *(vm->sp - 1);
|
||||
printf("%s\n", toString(vm, v)->data);
|
||||
printf("%s\n", toString(vm, v, false)->data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
register uint8_t* ip; //< Current instruction pointer.
|
||||
register Var* rbp; //< Stack base pointer register.
|
||||
register CallFrame* frame; //< Current call frame.
|
||||
Script* script; //< Currently executing script.
|
||||
register Script* script; //< Currently executing script.
|
||||
|
||||
#define PUSH(value) (*vm->sp++ = (value))
|
||||
#define POP() (*(--vm->sp))
|
||||
@ -215,6 +215,22 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
PUSH(VAR_FALSE);
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(PUSH_LIST):
|
||||
{
|
||||
List* list = newList(vm, (uint32_t)READ_SHORT());
|
||||
PUSH(VAR_OBJ(&list->_super));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(LIST_APPEND):
|
||||
{
|
||||
Var elem = POP();
|
||||
Var list = *(vm->sp - 1);
|
||||
ASSERT(IS_OBJ(list) && AS_OBJ(list)->type == OBJ_LIST, OOPS);
|
||||
varBufferWrite(&((List*)AS_OBJ(list))->elements, vm, elem);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_LOCAL_0):
|
||||
OPCODE(PUSH_LOCAL_1):
|
||||
OPCODE(PUSH_LOCAL_2):
|
||||
@ -260,7 +276,7 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
OPCODE(PUSH_GLOBAL):
|
||||
{
|
||||
int index = READ_SHORT();
|
||||
ASSERT(index < script->globals.count, "Oops a Bug report plese!");
|
||||
ASSERT(index < script->globals.count, OOPS);
|
||||
PUSH(script->globals.data[index]);
|
||||
DISPATCH();
|
||||
}
|
||||
@ -268,7 +284,7 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
OPCODE(STORE_GLOBAL):
|
||||
{
|
||||
int index = READ_SHORT();
|
||||
ASSERT(index < script->globals.count, "Oops a Bug report plese!");
|
||||
ASSERT(index < script->globals.count, OOPS);
|
||||
script->globals.data[index] = POP();
|
||||
DISPATCH();
|
||||
}
|
||||
@ -276,7 +292,7 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
OPCODE(PUSH_FN):
|
||||
{
|
||||
int index = READ_SHORT();
|
||||
ASSERT(index < script->functions.count, "Oops a Bug report plese!");
|
||||
ASSERT(index < script->functions.count, OOPS);
|
||||
Function* fn = script->functions.data[index];
|
||||
PUSH(VAR_OBJ(&fn->_super));
|
||||
DISPATCH();
|
||||
@ -301,7 +317,9 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) {
|
||||
|
||||
Function* fn = (Function*)AS_OBJ(*callable);
|
||||
if (fn->arity != argc) {
|
||||
|
||||
// -1 argument means multiple number of args.
|
||||
if (fn->arity != -1 && fn->arity != argc) {
|
||||
RUNTIME_ERROR("Expected excatly %d argument(s).", fn->arity);
|
||||
}
|
||||
|
||||
@ -330,8 +348,35 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(JUMP):
|
||||
OPCODE(JUMP_NOT):
|
||||
OPCODE(ITER) :
|
||||
{
|
||||
Var* iter_value = (vm->sp - 1);
|
||||
Var* iterator = (vm->sp - 2);
|
||||
Var* container = (vm->sp - 3);
|
||||
int jump_offset = READ_SHORT();
|
||||
if (!varIterate(vm, *container, iterator, iter_value)) {
|
||||
DROP(); //< Iter value.
|
||||
DROP(); //< Iterator.
|
||||
DROP(); //< Container.
|
||||
ip += jump_offset;
|
||||
}
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(JUMP): {
|
||||
int offset = READ_SHORT();
|
||||
ip += offset;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(LOOP): {
|
||||
int offset = READ_SHORT();
|
||||
ip -= offset;
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
||||
OPCODE(JUMP_IF):
|
||||
OPCODE(JUMP_IF_NOT):
|
||||
TODO;
|
||||
|
||||
@ -361,25 +406,40 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) {
|
||||
OPCODE(SET_ATTRIB):
|
||||
OPCODE(GET_SUBSCRIPT):
|
||||
OPCODE(SET_SUBSCRIPT):
|
||||
TODO;
|
||||
|
||||
OPCODE(NEGATIVE):
|
||||
{
|
||||
Var num = POP();
|
||||
if (!IS_NUM(num)) {
|
||||
RUNTIME_ERROR("Cannot negate a non numeric value.");
|
||||
}
|
||||
PUSH(VAR_NUM(-AS_NUM(num)));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(NOT):
|
||||
OPCODE(BIT_NOT):
|
||||
TODO;
|
||||
|
||||
OPCODE(ADD):
|
||||
PUSH(varAdd(vm, POP(), POP()));
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(SUBTRACT):
|
||||
PUSH(varSubtract(vm, POP(), POP()));
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(MULTIPLY):
|
||||
PUSH(varMultiply(vm, POP(), POP()));
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(DIVIDE):
|
||||
PUSH(varDivide(vm, POP(), POP()));
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
|
||||
OPCODE(MOD):
|
||||
|
Loading…
Reference in New Issue
Block a user