diff --git a/include/miniscript.h b/include/miniscript.h index 5f7e59f..40d5fd5 100644 --- a/include/miniscript.h +++ b/include/miniscript.h @@ -78,7 +78,7 @@ typedef struct { } MSConfiguration; typedef enum { - RESULT_SUCCESS, + RESULT_SUCCESS = 0, RESULT_COMPILE_ERROR, RESULT_RUNTIME_ERROR, } MSInterpretResult; diff --git a/src/compiler.c b/src/compiler.c index a33af6f..d5fbc9c 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -5,6 +5,9 @@ #include "compiler.h" +#include +#include + #include "core.h" #include "types/name_table.h" #include "types/gen/byte_buffer.h" @@ -481,10 +484,10 @@ static void eatNumber(Parser* parser) { // Read and ignore chars till it reach new line or EOF. static void skipLineComment(Parser* parser) { - char c = eatChar(parser); - - while (c != '\n' && c != '\0') { - c = eatChar(parser); + char c; + while ((c = peekChar(parser)) != '\0') { + eatChar(parser); + if (c == '\n') return; } } @@ -680,6 +683,30 @@ static bool match(Parser* self, TokenType expected) { return true; } +// Consume the the current token and if it's not [expected] emits error log +// and continue parsing for more error logs. +static void consume(Parser* self, TokenType expected, const char* err_msg) { + //ASSERT(expected != TK_LINE, "Can't match TK_LINE."); + //matchLine(self); + + lexToken(self); + if (self->previous.type != expected) { + parseError(self, "%s", err_msg); + + // If the next token is expected discard the current to minimize + // cascaded errors and continue parsing. + if (peek(self) == expected) { + lexToken(self); + + } else { + // Skip the current line, to prevent multiple caseaded errors on the + // same line. + skipLineComment(self); + } + } +} + + // Match one or more lines and return true if there any. static bool matchLine(Parser* parser) { if (peek(parser) != TK_LINE) return false; @@ -688,17 +715,20 @@ static bool matchLine(Parser* parser) { return true; } -// Match semi collon or multiple new lines. -static void consumeEndStatement(Parser* parser) { - bool consumed = false; - - // Semi collon must be on the same line. - if (peek(parser) == TK_SEMICOLLON) { - match(parser, TK_SEMICOLLON); - consumed = true; +// Match semi collon, multiple new lines or peek 'end' keyword. +static bool matchEndStatement(Parser* parser) { + if (match(parser, TK_SEMICOLLON)) { + skipNewLines(parser); + return true; } - if (matchLine(parser)) consumed = true; - if (!consumed && peek(parser) != TK_EOF) { + + if (matchLine(parser) || peek(parser) == TK_END || peek(parser) == TK_EOF) + return true; +} + +// Consume semi collon, multiple new lines or peek 'end' keyword. +static void consumeEndStatement(Parser* parser) { + if (!matchEndStatement(parser)) { parseError(parser, "Expected statement end with newline or ';'."); } } @@ -719,22 +749,14 @@ static void consumeStartBlock(Parser* parser) { } } -// Consume the the current token and if it's not [expected] emits error log -// and continue parsing for more error logs. -static void consume(Parser* self, TokenType expected, const char* err_msg) { - //ASSERT(expected != TK_LINE, "Can't match TK_LINE."); - //matchLine(self); - - lexToken(self); - if (self->previous.type != expected) { - parseError(self, "%s", err_msg); - - // If the next token is expected discard the current to minimize - // cascaded errors and continue parsing. - if (peek(self) == expected) { - lexToken(self); - } - } +// Returns a optional compound assignment. +static bool matchAssignment(Parser* parser) { + if (match(parser, TK_EQ)) return true; + if (match(parser, TK_PLUSEQ)) return true; + if (match(parser, TK_MINUSEQ)) return true; + if (match(parser, TK_STAREQ)) return true; + if (match(parser, TK_DIVEQ)) return true; + return false; } /***************************************************************************** @@ -932,7 +954,7 @@ static GrammarRule* getRule(TokenType type) { } // Emit variable store. -static void _emitStoreVariable(Compiler* compiler, int index, bool global) { +static void emitStoreVariable(Compiler* compiler, int index, bool global) { if (global) { emitOpcode(compiler, OP_STORE_GLOBAL); emitShort(compiler, index); @@ -947,7 +969,7 @@ static void _emitStoreVariable(Compiler* compiler, int index, bool global) { } } -static void _emitPushVariable(Compiler* compiler, int index, bool global) { +static void emitPushVariable(Compiler* compiler, int index, bool global) { if (global) { emitOpcode(compiler, OP_PUSH_GLOBAL); emitShort(compiler, index); @@ -977,45 +999,56 @@ static void exprFunc(Compiler* compiler, bool can_assign) { // Local/global variables, script/native/builtin functions name. static void exprName(Compiler* compiler, bool can_assign) { - const char* name_start = compiler->parser.previous.start; - int name_len = compiler->parser.previous.length; - int name_line = compiler->parser.previous.line; + + Parser* parser = &compiler->parser; + + const char* name_start = parser->previous.start; + int name_len = parser->previous.length; + int name_line = parser->previous.line; NameSearchResult result = compilerSearchName(compiler, name_start, name_len); if (result.type == NAME_NOT_DEFINED) { - if (can_assign && match(&compiler->parser, TK_EQ)) { + if (can_assign && match(parser, TK_EQ)) { int index = compilerAddVariable(compiler, name_start, name_len, name_line); compileExpression(compiler); - _emitStoreVariable(compiler, index, compiler->scope_depth == DEPTH_GLOBAL); + emitStoreVariable(compiler, index, compiler->scope_depth == DEPTH_GLOBAL); return; } else { - parseError(&compiler->parser, "Name \"%.*s\" is not defined.", name_len, - name_start); + parseError(parser, "Name \"%.*s\" is not defined.", name_len, name_start); } } // TODO: can_assign and += -= etc. - switch (result.type) { case NAME_LOCAL_VAR: - if (can_assign && match(&compiler->parser, TK_EQ)) { - compileExpression(compiler); - _emitStoreVariable(compiler, result.index, false); - } else { - _emitPushVariable(compiler, result.index, false); - } - return; - case NAME_GLOBAL_VAR: - if (can_assign && match(&compiler->parser, TK_EQ)) { - compileExpression(compiler); - _emitStoreVariable(compiler, result.index, true); - + + if (can_assign && matchAssignment(parser)) { + TokenType assignment = parser->previous.type; + if (assignment != TK_EQ) { + emitPushVariable(compiler, result.index, result.type == NAME_GLOBAL_VAR); + compileExpression(compiler); + + switch (assignment) { + case TK_PLUSEQ: emitOpcode(compiler, OP_ADD); break; + case TK_MINUSEQ: emitOpcode(compiler, OP_SUBTRACT); break; + case TK_STAREQ: emitOpcode(compiler, OP_MULTIPLY); break; + case TK_DIVEQ: emitOpcode(compiler, OP_DIVIDE); break; + default: + UNREACHABLE(); + break; + + } + } else { + compileExpression(compiler); + } + + emitStoreVariable(compiler, result.index, result.type == NAME_GLOBAL_VAR); + } else { - emitOpcode(compiler, OP_PUSH_GLOBAL); - emitShort(compiler, result.index); + emitPushVariable(compiler, result.index, result.type == NAME_GLOBAL_VAR); } return; @@ -1345,7 +1378,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { vmPushTempRef(compiler->vm, &func->_super); functionBufferWrite(&compiler->script->functions, compiler->vm, func); vmPopTempRef(compiler->vm); - int fn_index = compiler->script->functions.count - 1; + int fn_index = (int)compiler->script->functions.count - 1; Func curr_func; curr_func.outer_func = compiler->func; @@ -1574,7 +1607,7 @@ static void compileStatement(Compiler* compiler) { return; } - if (match(parser, TK_SEMICOLLON)|| match(parser, TK_LINE)) { + if (matchEndStatement(parser)) { emitOpcode(compiler, OP_PUSH_NULL); emitOpcode(compiler, OP_RETURN); } else { diff --git a/src/core.c b/src/core.c index c0d2fcc..8013689 100644 --- a/src/core.c +++ b/src/core.c @@ -6,6 +6,8 @@ #include "core.h" #include +#include + #include "vm.h" typedef struct { @@ -50,7 +52,7 @@ int findBuiltinFunction(const char* name, int length) { // Validators ///////////////////////////////////////////////////////////////// // Check if a numeric value bool/number and set [value]. -static bool isNumeric(Var var, double* value) { +static inline bool isNumeric(Var var, double* value) { if (IS_BOOL(var)) { *value = AS_BOOL(var); return true; @@ -170,6 +172,13 @@ void coreImport(MSVM* vm) { } } +// TODO: Temp function to benchmark. +void coreClock(MSVM* vm) { + RET(VAR_NUM((double)clock() / CLOCKS_PER_SEC)); +} + + + void initializeCore(MSVM* vm) { int i = 0; //< Iterate through builtins. @@ -191,6 +200,9 @@ void initializeCore(MSVM* vm) { initializeBuiltinFN(vm, &builtins[i++], "print", -1, corePrint); initializeBuiltinFN(vm, &builtins[i++], "import", 1, coreImport); + // FIXME: move this. temp for benchmarking. + initializeBuiltinFN(vm, &builtins[i++], "clock", 0, coreClock); + // Sentinal to mark the end of the array. initializeBuiltinFN(vm, &builtins[i], NULL, 0, NULL); @@ -213,7 +225,15 @@ Var varAdd(MSVM* vm, Var v1, Var v2) { } Var varSubtract(MSVM* vm, Var v1, Var v2) { - TODO; + double d1, d2; + if (isNumeric(v1, &d1)) { + if (validateNumeric(vm, v2, &d2, "Right operand")) { + return VAR_NUM(d1 - d2); + } + return VAR_NULL; + } + + TODO; //string addition/ array addition etc. return VAR_NULL; } @@ -236,6 +256,27 @@ Var varDivide(MSVM* vm, Var v1, Var v2) { return VAR_NULL; } +bool varGreater(Var v1, Var v2) { + + double d1, d2; + if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) { + return d1 > d2; + } + + TODO; + return false; +} + +bool varLesser(Var v1, Var v2) { + double d1, d2; + if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) { + return d1 < d2; + } + + TODO; + return false; +} + bool varIterate(MSVM* vm, Var seq, Var* iterator, Var* value) { #ifdef DEBUG diff --git a/src/core.h b/src/core.h index c347e24..5e15264 100644 --- a/src/core.h +++ b/src/core.h @@ -24,6 +24,9 @@ Var varSubtract(MSVM* vm, Var v1, Var v2); Var varMultiply(MSVM* vm, Var v1, Var v2); Var varDivide(MSVM* vm, Var v1, Var v2); +bool varGreater(Var v1, Var v2); +bool varLesser(Var v1, Var v2); + // Functions ////////////////////////////////////////////////////////////////// // Parameter [iterator] should be VAR_NULL before starting the iteration. diff --git a/src/var.c b/src/var.c index 8a4ee12..2df5e43 100644 --- a/src/var.c +++ b/src/var.c @@ -3,6 +3,8 @@ * Licensed under: MIT License */ +#include + #include "var.h" #include "vm.h" @@ -187,3 +189,29 @@ String* toString(MSVM* vm, Var v, bool recursive) { UNREACHABLE(); return NULL; } + +bool toBool(Var v) { + if (IS_BOOL(v)) return AS_BOOL(v); + if (IS_NULL(v)) return false; + if (IS_NUM(v)) { + return AS_NUM(v) != 0; + } + + ASSERT(IS_OBJ(v), OOPS); + Object* o = AS_OBJ(v); + switch (o->type) { + case OBJ_STRING: return ((String*)o)->length != 0; + + case OBJ_LIST: return ((List*)o)->elements.count != 0; + case OBJ_MAP: TODO; + case OBJ_RANGE: TODO; // Nout sure. + case OBJ_SCRIPT: // [[FALLTHROUGH]] + case OBJ_FUNC: + case OBJ_USER: + return true; + default: + UNREACHABLE(); + } + + return true; +} diff --git a/src/var.h b/src/var.h index c8b5b8e..eeb2820 100644 --- a/src/var.h +++ b/src/var.h @@ -303,4 +303,7 @@ bool isVauesSame(Var v1, Var v2); // It's an internal use (or may be I could make a wrapper around). String* toString(MSVM* vm, Var v, bool _recursive); +// Returns the truthy value of the var. +bool toBool(Var v); + #endif // VAR_H diff --git a/src/vm.c b/src/vm.c index ae51636..0507c9f 100644 --- a/src/vm.c +++ b/src/vm.c @@ -14,7 +14,7 @@ #define INITIAL_CALL_FRAMES 4 // Minimum size of the stack. -#define MIN_STACK_SIZE 16 +#define MIN_STACK_SIZE 128 void* vmRealloc(MSVM* self, void* memory, size_t old_size, size_t new_size) { @@ -57,6 +57,8 @@ void vmPopTempRef(MSVM* self) { * RUNTIME * *****************************************************************************/ +#ifdef DEBUG +#include // TODO: A function for quick debug. REMOVE. void _printStackTop(MSVM* vm) { if (vm->sp != vm->stack) { @@ -64,6 +66,7 @@ void _printStackTop(MSVM* vm) { printf("%s\n", toString(vm, v, false)->data); } } +#endif static void ensureStackSize(MSVM* vm, int size) { if (vm->stack_size > size) return; @@ -377,8 +380,24 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { OPCODE(JUMP_IF): + { + Var cond = POP(); + int offset = READ_SHORT(); + if (toBool(cond)) { + ip += offset; + } + DISPATCH(); + } + OPCODE(JUMP_IF_NOT): - TODO; + { + Var cond = POP(); + int offset = READ_SHORT(); + if (!toBool(cond)) { + ip += offset; + } + DISPATCH(); + } OPCODE(RETURN): { @@ -419,6 +438,12 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { } OPCODE(NOT): + { + Var val = POP(); + PUSH(VAR_BOOL(!toBool(val))); + DISPATCH(); + } + OPCODE(BIT_NOT): TODO; @@ -452,9 +477,23 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { OPCODE(OR): OPCODE(EQEQ): OPCODE(NOTEQ): + TODO; + OPCODE(LT): + { + PUSH(VAR_BOOL(varLesser(POP(), POP()))); + DISPATCH(); + } + OPCODE(LTEQ): + TODO; + OPCODE(GT): + { + PUSH(VAR_BOOL(varGreater(POP(), POP()))); + DISPATCH(); + } + OPCODE(GTEQ): TODO; diff --git a/test/main.c b/test/main.c index d1c5ee3..b748d59 100644 --- a/test/main.c +++ b/test/main.c @@ -63,6 +63,19 @@ MSLoadScriptResult loadScript(MSVM* vm, const char* path) { int main(int argc, char** argv) { + const char* notice = + "MiniScript " MS_VERSION_STRING " (https://github.com/ThakeeNathees/miniscript/)\n" + "Copyright(c) 2020 - 2021 ThakeeNathees.\n" + "Free and open source software under the terms of the MIT license.\n"; + const char* help = "Usage: miniscript \n"; + + if (argc < 2) { + printf("%s\n%s", notice, help); + return 0; + } + + const char* source_path = argv[1]; + MSConfiguration config; config.error_fn = errorPrint; config.write_fn = writeFunction; @@ -70,19 +83,7 @@ int main(int argc, char** argv) { config.load_script_done_fn = loadScriptDone; MSVM* vm = msNewVM(&config); - MSInterpretResult result = msInterpret(vm, "build/test.ms"); + MSInterpretResult result = msInterpret(vm, source_path); - //Script* script = compileSource(vm, "../some/path/file.ms"); - //vmRunScript(vm, script); - // - //ByteBuffer* bytes = &script->body->fn->opcodes; - //for (int i = 0; i < bytes->count; i++) { - // const char* op = "(???)"; - // if (bytes->data[i] <= (int)OP_END) { - // op = opnames[bytes->data[i]]; - // } - // printf("%s %i\n", op, bytes->data[i]); - //} - - return 0; + return result; }