diff --git a/README.md b/README.md index efe89c7..0230381 100644 --- a/README.md +++ b/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)) ``` diff --git a/src/common.h b/src/common.h index 8aa8e31..6364c01 100644 --- a/src/common.h +++ b/src/common.h @@ -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) \ diff --git a/src/compiler.c b/src/compiler.c index 0cd95ac..7cc16d5 100644 --- a/src/compiler.c +++ b/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)) { diff --git a/src/core.c b/src/core.c index 706f09d..c0d2fcc 100644 --- a/src/core.c +++ b/src/core.c @@ -4,6 +4,8 @@ */ #include "core.h" + +#include #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; - // 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); - } else { - str = toString(vm, arg1); + 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(arg) && AS_OBJ(arg)->type == OBJ_STRING) { + str = (String*)AS_OBJ(arg); + } else { + 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, str->data); + vm->config.write_fn(vm, "\n"); } @@ -107,49 +175,27 @@ void initializeCore(MSVM* vm) { int i = 0; //< Iterate through builtins. // Initialize builtin functions. - initializeBuiltinFN(vm, &builtins[i++], "is_null", 1, coreIsNull); - initializeBuiltinFN(vm, &builtins[i++], "is_bool", 1, coreIsBool); - initializeBuiltinFN(vm, &builtins[i++], "is_num", 1, coreIsNum); + initializeBuiltinFN(vm, &builtins[i++], "is_null", 1, coreIsNull); + initializeBuiltinFN(vm, &builtins[i++], "is_bool", 1, coreIsBool); + 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); initializeBuiltinFN(vm, &builtins[i++], "is_script", 1, coreIsScript); 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++], "import", 1, coreImport); + initializeBuiltinFN(vm, &builtins[i++], "to_string", 1, coreToString); + initializeBuiltinFN(vm, &builtins[i++], "print", -1, corePrint); + initializeBuiltinFN(vm, &builtins[i++], "import", 1, coreImport); // Sentinal to mark the end of the array. initializeBuiltinFN(vm, &builtins[i], NULL, 0, NULL); } -// 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; } \ No newline at end of file diff --git a/src/opcodes.h b/src/opcodes.h index 960482d..dd95cd9 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -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. diff --git a/src/var.c b/src/var.c index e3ff776..ee245a3 100644 --- a/src/var.c +++ b/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: - return newString(vm, ((String*)obj)->data, ((String*)obj)->length); + { + // 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; diff --git a/src/var.h b/src/var.h index ae56131..c8b5b8e 100644 --- a/src/var.h +++ b/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 diff --git a/src/vm.c b/src/vm.c index 3421d44..dd443dc 100644 --- a/src/vm.c +++ b/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):