From 09d397bbfc699a44fa11ec35315a9ee035798b4d Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Tue, 9 Feb 2021 13:51:10 +0530 Subject: [PATCH] opcodes implemented --- src/common.h | 12 +- src/compiler.c | 421 ++++++++++++++++++++++++++++++++---- src/opcodes.h | 179 +++++++++++++++ src/types/buffer.template.c | 1 + src/types/buffer.template.h | 6 - src/var.c | 20 +- src/var.h | 10 +- src/vm.h | 6 + test/main.c | 6 +- 9 files changed, 601 insertions(+), 60 deletions(-) create mode 100644 src/opcodes.h diff --git a/src/common.h b/src/common.h index ee34131..a81d3f5 100644 --- a/src/common.h +++ b/src/common.h @@ -37,6 +37,16 @@ #define MS_PUBLIC #endif +#define STRINGIFY(x) TOSTRING(x) +#define TOSTRING(x) #x + +// The factor by which a buffer will grow when it's capacity reached. +#define GROW_FACTOR 2 + +// The initial capacity of a buffer. +#define MIN_CAPACITY 8 + + // Unique number to identify for various cases. typedef uint32_t ID; @@ -77,7 +87,7 @@ typedef struct Function Function; #define UNREACHABLE() \ do { \ fprintf(stderr, "Execution reached an unreachable path\n" \ - "\tat %s() (%s:%i)\n", __FILE__, __LINE__, __func__); \ + "\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \ abort(); \ } while (false) diff --git a/src/compiler.c b/src/compiler.c index dfc9fc9..111036c 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -15,6 +15,16 @@ // which is using a single byte value to identify the local. #define MAX_VARIABLES 256 +// The maximum number of constant literal a script can contain. Also it's +// limited by it's opcode which is using a short value to identify. +#define MAX_CONSTANTS (1 << 16) + +// The maximum address possible to jump. Similar limitation as above. +#define MAX_JUMP (1 << 16) + +// Max number of break statement in a loop statement to patch. +#define MAX_BREAK_PATCH 256 + typedef enum { TK_ERROR = 0, @@ -246,16 +256,16 @@ typedef struct { typedef struct sLoop { // Index of the loop's start instruction where the execution will jump - // back to once it reach the loop end. + // back to once it reach the loop end or continue used. int start; // Index of the jump out address instruction to patch it's value once done // compiling the loop. int exit_jump; - // Index of the first body instruction. Needed to start patching jump - // address from which till the loop end. - int body; + // Array of address indexes to patch break address. + int patches[MAX_BREAK_PATCH]; + int patch_count; // The outer loop of the current loop used to set and reset the compiler's // current loop context. @@ -275,11 +285,24 @@ struct Compiler { Variable variables[MAX_VARIABLES]; //< Variables in the current context. int var_count; //< Number of locals in [variables]. + int stack_size; //< Current size including locals ind temps. + // TODO: compiler should mark Script* below not to be garbage collected. - Script* script; //< Current script. - Loop* loop; //< Current loop. - Function* fn; //< Current function. + Script* script; //< Current script. + Loop* loop; //< Current loop. + Function* function; //< Current function. +}; + +typedef struct { + int params; + int stack; +} OpInfo; + +static OpInfo opcode_info[] = { + #define OPCODE(name, params, stack) { params, stack }, + #include "opcodes.h" + #undef OPCODE }; /***************************************************************************** @@ -289,6 +312,7 @@ struct Compiler { static void reportError(Parser* parser, const char* file, int line, const char* fmt, va_list args) { parser->has_errors = true; + ASSERT(false, "TODO:"); // TODO: parser->vm->config.error_fn(...) } @@ -337,7 +361,7 @@ static void eatString(Parser* parser) { if (c == '"') break; if (c == '\0') { - // TODO: syntaxError() + lexError(parser, "Non terminated string."); // Null byte is required by TK_EOF. parser->current_char--; @@ -353,7 +377,7 @@ static void eatString(Parser* parser) { case 't': byteBufferWrite(&buff, parser->vm, '\t'); break; default: - // TODO: syntaxError("Error: invalid escape character") + lexError(parser, "Error: invalid escape character"); break; } } else { @@ -429,7 +453,9 @@ static void eatNumber(Parser* parser) { errno = 0; Var value = VAR_NUM(strtod(parser->token_start, NULL)); if (errno == ERANGE) { - // TODO: error() literal number is too large. + const char* start = parser->token_start; + int len = (parser->current_char - start); + lexError(parser, "Literal is too large (%.*s)", len, start); value = VAR_NUM(0); } @@ -577,9 +603,9 @@ static void lexToken(Parser* parser) { eatName(parser); } else { if (c >= 32 && c <= 126) { - // TODO: syntaxError("Invalid character %c", c); + lexError(parser, "Invalid character %c", c); } else { - // TODO: syntaxError("Invalid byte 0x%x", (uint8_t)c); + lexError(parser, "Invalid byte 0x%x", (uint8_t)c); } setNextToken(parser, TK_ERROR); } @@ -645,23 +671,33 @@ static bool matchLine(Parser* parser) { } // Match semi collon or multiple new lines. -static void matchEndStatement(Parser* parser) { +static void consumeEndStatement(Parser* parser) { + bool consumed = false; // Semi collon must be on the same line. - if (peek(parser) == TK_SEMICOLLON) + if (peek(parser) == TK_SEMICOLLON) { match(parser, TK_SEMICOLLON); - - skipNewLines(parser); + consumed = true; + } + if (matchLine(parser)) consumed = true; + if (!consumed && peek(parser) != TK_EOF) { + parseError(parser, "Expected statement end with newline or ';'."); + } } // Match optional "do" keyword and new lines. -static void matchStartBlock(Parser* parser) { +static void consumeStartBlock(Parser* parser) { + bool consumed = false; // "do" must be on the same line. - if (peek(parser) == TK_DO) + if (peek(parser) == TK_DO) { match(parser, TK_DO); - - skipNewLines(parser); + consumed = true; + } + if (matchLine(parser)) consumed = true; + if (!consumed) { + parseError(parser, "Expected enter block with newline or 'do'."); + } } // Consume the the current token and if it's not [expected] emits error log @@ -673,7 +709,7 @@ static void consume(Parser* self, TokenType expected, const char* err_msg) { lexToken(self); if (self->previous.type != expected) { - // TODO: syntaxError(err_msg); + parseError(self, "%s", err_msg); // If the next token is expected discard the current to minimize // cascaded errors and continue parsing. @@ -687,15 +723,22 @@ static void consume(Parser* self, TokenType expected, const char* err_msg) { * PARSING GRAMMAR * *****************************************************************************/ -// Forward declaration of grammar functions. +// Forward declaration of codegen functions. +static void emitOpcode(Compiler* compiler, Opcode opcode); +static int emitByte(Compiler* compiler, int byte); +static int emitShort(Compiler* compiler, int arg); +static int compilerAddConstant(Compiler* compiler, Var value); +// Forward declaration of grammar functions. +static void parsePrecedence(Compiler* compiler, Precedence precedence); + +static void compileExpression(Compiler* compiler); static void exprAssignment(Compiler* compiler, bool can_assign); // Bool, Num, String, Null, -and- bool_t, Array_t, String_t, ... static void exprLiteral(Compiler* compiler, bool can_assign); static void exprName(Compiler* compiler, bool can_assign); - static void exprBinaryOp(Compiler* compiler, bool can_assign); static void exprUnaryOp(Compiler* compiler, bool can_assign); @@ -732,7 +775,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_PIPE */ { NULL, exprBinaryOp, PREC_BITWISE_OR }, /* TK_CARET */ { NULL, exprBinaryOp, PREC_BITWISE_XOR }, /* TK_PLUS */ { NULL, exprBinaryOp, PREC_TERM }, - /* TK_MINUS */ { NULL, exprBinaryOp, PREC_TERM }, + /* TK_MINUS */ { exprUnaryOp, exprBinaryOp, PREC_TERM }, /* TK_STAR */ { NULL, exprBinaryOp, PREC_FACTOR }, /* TK_FSLASH */ { NULL, exprBinaryOp, PREC_FACTOR }, /* TK_BSLASH */ NO_RULE, @@ -760,7 +803,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_IN */ { NULL, exprBinaryOp, PREC_IN }, /* TK_AND */ { NULL, exprBinaryOp, PREC_LOGICAL_AND }, /* TK_OR */ { NULL, exprBinaryOp, PREC_LOGICAL_OR }, - /* TK_NOT */ { NULL, exprUnaryOp, PREC_LOGICAL_NOT }, + /* TK_NOT */ { exprUnaryOp, NULL, PREC_LOGICAL_NOT }, /* TK_TRUE */ { exprLiteral, NULL, NO_INFIX }, /* TK_FALSE */ { exprLiteral, NULL, NO_INFIX }, /* TK_BOOL_T */ { exprLiteral, NULL, NO_INFIX }, @@ -789,23 +832,93 @@ static GrammarRule* getRule(TokenType type) { return &(rules[(int)type]); } -static void exprAssignment(Compiler* compiler, bool can_assign) { /*TODO*/ } +static void exprAssignment(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } -static void exprLiteral(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprName(Compiler* compiler, bool can_assign) { /*TODO*/ } +static void exprLiteral(Compiler* compiler, bool can_assign) { + Token* value = &compiler->parser.previous; + int index = compilerAddConstant(compiler, value->value); + emitOpcode(compiler, OP_CONSTANT); + emitShort(compiler, index); +} +static void exprName(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } -static void exprBinaryOp(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprUnaryOp(Compiler* compiler, bool can_assign) { /*TODO*/ } +static void exprBinaryOp(Compiler* compiler, bool can_assign) { + TokenType op = compiler->parser.previous.type; + skipNewLines(&compiler->parser); + parsePrecedence(compiler, (Precedence)(getRule(op)->precedence + 1)); -static void exprGrouping(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprArray(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprMap(Compiler* compiler, bool can_assign) { /*TODO*/ } + switch (op) { + case TK_DOTDOT: emitOpcode(compiler, OP_RANGE); break; + case TK_PERCENT: emitOpcode(compiler, OP_MOD); break; + case TK_AMP: emitOpcode(compiler, OP_BIT_AND); break; + case TK_PIPE: emitOpcode(compiler, OP_BIT_OR); break; + case TK_CARET: emitOpcode(compiler, OP_BIT_XOR); break; + case TK_PLUS: emitOpcode(compiler, OP_ADD); break; + case TK_MINUS: emitOpcode(compiler, OP_SUBTRACT); break; + case TK_STAR: emitOpcode(compiler, OP_MULTIPLY); break; + case TK_FSLASH: emitOpcode(compiler, OP_DIVIDE); break; + case TK_GT: emitOpcode(compiler, OP_GT); break; + case TK_LT: emitOpcode(compiler, OP_LT); break; + case TK_EQEQ: emitOpcode(compiler, OP_EQEQ); break; + case TK_NOTEQ: emitOpcode(compiler, OP_NOTEQ); break; + case TK_GTEQ: emitOpcode(compiler, OP_GTEQ); break; + case TK_LTEQ: emitOpcode(compiler, OP_LTEQ); break; + case TK_SRIGHT: emitOpcode(compiler, OP_BIT_RSHIFT); break; + case TK_SLEFT: emitOpcode(compiler, OP_BIT_LSHIFT); break; + case TK_IS: emitOpcode(compiler, OP_IS); break; + case TK_IN: emitOpcode(compiler, OP_IN); break; + case TK_AND: emitOpcode(compiler, OP_AND); break; + case TK_OR: emitOpcode(compiler, OP_OR); break; + default: + UNREACHABLE(); + } +} -static void exprCall(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprAttrib(Compiler* compiler, bool can_assign) { /*TODO*/ } -static void exprSubscript(Compiler* compiler, bool can_assign) { /*TODO*/ } +static void exprUnaryOp(Compiler* compiler, bool can_assign) { + TokenType op = compiler->parser.previous.type; + skipNewLines(&compiler->parser); + parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); + switch (op) { + case TK_TILD: emitOpcode(compiler, OP_BIT_NOT); break; + case TK_MINUS: emitOpcode(compiler, OP_NEGATIVE); break; + case TK_NOT: emitOpcode(compiler, OP_NOT); break; + default: + UNREACHABLE(); + } +} + +static void exprGrouping(Compiler* compiler, bool can_assign) { + compileExpression(compiler); + consume(&compiler->parser, TK_RPARAN, "Expected ')' after expression "); +} + +static void exprArray(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } +static void exprMap(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } + +static void exprCall(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } +static void exprAttrib(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } +static void exprSubscript(Compiler* compiler, bool can_assign) { ASSERT(false, "TODO:"); } + +static void parsePrecedence(Compiler* compiler, Precedence precedence) { + lexToken(&compiler->parser); + GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix; + + if (prefix == NULL) { + parseError(&compiler->parser, "Expected an expression."); + return; + } + + bool can_assign = precedence <= PREC_ASSIGNMENT; + prefix(compiler, can_assign); + + while (getRule(compiler->parser.current.type)->precedence >= precedence) { + lexToken(&compiler->parser); + GrammarFn infix = getRule(compiler->parser.previous.type)->infix; + infix(compiler, can_assign); + } +} /***************************************************************************** * COMPILING * @@ -855,6 +968,7 @@ static void compilerInit(Compiler* compiler, MSVM* vm, const char* source, vm->compiler = compiler; compiler->scope_depth = -1; compiler->var_count = 0; + compiler->stack_size = 0; Loop* loop = NULL; Function* fn = NULL; } @@ -901,6 +1015,97 @@ static int compilerAddVariable(Compiler* compiler, const char* name, return compiler->var_count++; } +// Add a literal constant to scripts literals and return it's index. +static int compilerAddConstant(Compiler* compiler, Var value) { + VarBuffer* literals = &compiler->script->literals; + + for (int i = 0; i < literals->count; i++) { + if (isVauesSame(literals->data[i], value)) { + return i; + } + } + + // Add new constant to script. + if (literals->count < MAX_CONSTANTS) { + varBufferWrite(literals, compiler->vm, value); + } else { + parseError(&compiler->parser, "A script should contain at most %d " + "unique constants.", MAX_CONSTANTS); + } + return (int)literals->count - 1; +} + +// Enters inside a block. +static void compilerEnterBlock(Compiler* compiler) { + compiler->scope_depth++; +} + +// Exits a block. +static void compilerExitBlock(Compiler* compiler) { + ASSERT(compiler->scope_depth > -1, "Cannot exit toplevel."); + + while (compiler->variables[compiler->var_count - 1].depth >= + compiler->scope_depth) { + compiler->var_count--; + compiler->stack_size--; + } + compiler->scope_depth--; +} + +/***************************************************************************** + * COMPILING (EMIT BYTECODE) * + *****************************************************************************/ + +// Emit a single byte and return it's index. +static int emitByte(Compiler* compiler, int byte) { + + byteBufferWrite(&compiler->function->fn->opcodes, compiler->vm, + (uint8_t)byte); + intBufferWrite(&compiler->function->fn->oplines, compiler->vm, + compiler->parser.previous.line); + return (int)compiler->function->fn->opcodes.count - 1; +} + +// Emit 2 bytes argument as big indian. return it's starting index. +static int emitShort(Compiler* compiler, int arg) { + emitByte(compiler, (arg >> 8) & 0xff); + return emitByte(compiler, arg & 0xff) - 1; +} + +// Emits an instruction and update stack size (variable stack size opcodes +// should be handled). +static void emitOpcode(Compiler* compiler, Opcode opcode) { + emitByte(compiler, (int)opcode); + + compiler->stack_size += opcode_info[opcode].stack; + if (compiler->stack_size > compiler->function->fn->stack_size) { + compiler->function->fn->stack_size = compiler->stack_size; + } +} + +// Emits a constant value if it doesn't exists on the current script it'll make +// one. +static void emitConstant(Compiler* compiler, Var value) { + int index = compilerAddConstant(compiler, value); + emitOpcode(compiler, OP_CONSTANT); + emitShort(compiler, index); +} + +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."); + + compiler->function->fn->opcodes.data[addr_index] = (jump_to >> 8) & 0xff; + compiler->function->fn->opcodes.data[addr_index + 1] = jump_to & 0xff; +} + + /***************************************************************************** + * COMPILING (PARSE TOPLEVEL) * + *****************************************************************************/ + +static void compileStatement(Compiler* compiler); +static void compileBlockBody(Compiler* compiler, bool if_body); + static void compileFunction(Compiler* compiler, bool is_native) { Parser* parser = &compiler->parser; @@ -926,7 +1131,7 @@ static void compileFunction(Compiler* compiler, bool is_native) { functionBufferWrite(&compiler->script->functions, compiler->vm, func); vmPopTempRef(compiler->vm); - compiler->fn = func; + compiler->function = func; consume(parser, TK_LPARAN, "Expected '(' after function name."); @@ -937,34 +1142,153 @@ static void compileFunction(Compiler* compiler, bool is_native) { int predef = compilerSearchVariables(compiler, parser->previous.start, parser->previous.length, SCOPE_CURRENT); if (predef != -1) { - // TODO: error("Multiple definition of a parameter"); + parseError(parser, "Multiple definition of a parameter"); } match(parser, TK_COMMA); } consume(parser, TK_RPARAN, "Expected ')' after parameters end."); - matchEndStatement(parser); + consumeEndStatement(parser); if (is_native) { // Done here. compiler->scope_depth--; // Parameter scope. - compiler->fn = NULL; + compiler->function = NULL; return; } - - // TODO: Compile body. + + compileBlockBody(compiler, false); compiler->scope_depth--; // Parameter scope. - compiler->fn = NULL; + compiler->function = compiler->script->body; } -/***************************************************************************** - * COMPILING (STATEMENTS) * - *****************************************************************************/ +// Finish a block body. +static void compileBlockBody(Compiler* compiler, bool if_body) { + compilerEnterBlock(compiler); + + TokenType next = peek(&compiler->parser); + while (!(next == TK_END || next == TK_EOF || ( + if_body && (next == TK_ELSE || next == TK_ELIF)))) { + + compileStatement(compiler); + next = peek(&compiler->parser); + } + + compilerExitBlock(compiler); +} + +// Compiles an expression. An expression will result a value on top of the +// stack. +static void compileExpression(Compiler* compiler) { + parsePrecedence(compiler, PREC_LOWEST); +} + +static void compileIfStatement(Compiler* compiler) { + + compileExpression(compiler); //< Condition. + emitOpcode(compiler, OP_JUMP_IF_NOT); + int ifpatch = emitByte(compiler, 0xffff); //< Will be patched. + + consumeStartBlock(&compiler->parser); + + compileBlockBody(compiler, true); + + if (match(&compiler->parser, TK_ELIF)) { + patchJump(compiler, ifpatch); + compileBlockBody(compiler, true); + + } else if (match(&compiler->parser, TK_ELSE)) { + patchJump(compiler, ifpatch); + compileBlockBody(compiler, false); + + } else { + patchJump(compiler, ifpatch); + } +} + +static void compileWhileStatement(Compiler* compiler) { + Loop loop; + loop.start = (int)compiler->function->fn->opcodes.count; + loop.patch_count = 0; + loop.outer_loop = compiler->loop; + compiler->loop = &loop; + + 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); + + 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; +} + +static void compileForStatement(Compiler* compiler) { + ASSERT(false, "TODO:"); +} // Compiles a statement. Assignment could be an assignment statement or a new // variable declaration, which will be handled. static void compileStatement(Compiler* compiler) { - // TODO: + Parser* parser = &compiler->parser; + if (match(parser, TK_BREAK)) { + if (compiler->loop == NULL) { + parseError(parser, "Cannot use 'break' outside a loop."); + return; + } + + ASSERT(compiler->loop->patch_count < MAX_BREAK_PATCH, + "Too many break statements (" STRINGIFY(MAX_BREAK_PATCH) ")." ); + + emitOpcode(compiler, OP_JUMP); + int patch = emitByte(compiler, 0xffff); //< Will be patched. + compiler->loop->patches[compiler->loop->patch_count++] = patch; + + } else if (match(parser, TK_CONTINUE)) { + if (compiler->loop == NULL) { + parseError(parser, "Cannot use 'continue' outside a loop."); + return; + } + + emitOpcode(compiler, OP_JUMP); + emitShort(compiler, compiler->loop->start); + + } else if (match(parser, TK_RETURN)) { + + if (compiler->scope_depth == -1) { + parseError(parser, "Invalid 'return' outside a function."); + return; + } + + if (peek(parser) == TK_SEMICOLLON || peek(parser) == TK_LINE) { + emitOpcode(compiler, OP_PUSH_NULL); + emitOpcode(compiler, OP_RETURN); + } else { + compileExpression(compiler); //< Return value is at stack top. + emitOpcode(compiler, OP_RETURN); + } + } else if (match(parser, TK_IF)) { + compileIfStatement(compiler); + + } else if (match(parser, TK_WHILE)) { + compileWhileStatement(compiler); + + } else if (match(parser, TK_FOR)) { + compileForStatement(compiler); + + } else { + compileExpression(compiler); + emitOpcode(compiler, OP_POP); + } } Script* compileSource(MSVM* vm, const char* path) { @@ -983,6 +1307,7 @@ Script* compileSource(MSVM* vm, const char* path) { Script* script = newScript(vm); compiler.script = script; + compiler.function = script->body; // Parser pointer for quick access. Parser* parser = &compiler.parser; @@ -1002,7 +1327,7 @@ Script* compileSource(MSVM* vm, const char* path) { } else if (match(parser, TK_IMPORT)) { // TODO: import statement must be first of all other. - + ASSERT(false, "TODO:"); } else { compileStatement(&compiler); } diff --git a/src/opcodes.h b/src/opcodes.h new file mode 100644 index 0000000..d37e29d --- /dev/null +++ b/src/opcodes.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 Thakee Nathees + * Licensed under: MIT License + */ + +// Opcodes X macro (http://en.wikipedia.org/wiki/X_Macro) should be included +// in the source where it'll be used. Required to define the following... +// +// #define OPCODE(name, params, stack) +// #include "opcodes.h" +// +// first parameter is the opcode name, 2nd will be the size of the parameter +// in bytes 3 one is how many stack slots it'll take after executing the +// instruction. + +// Load the constant at index [arg] from the script's literals. +// params: 2 byte (uint16_t) index value. +OPCODE(CONSTANT, 2, 1) + +// Push null on the stack. +OPCODE(PUSH_NULL, 0, 0) + +// Push true on the stack. +OPCODE(PUSH_TRUE, 0, 0) + +// Push false on the stack. +OPCODE(PUSH_FALSE, 0, 0) + +// Push stack local on top of the stack. Locals at 0 to 8 marked explicitly +// since it's performance criticle. +// params: PUSH_LOCAL_N -> 2 bytes (uint16_t) count value. +OPCODE(PUSH_LOCAL_0, 0, 1) +OPCODE(PUSH_LOCAL_1, 0, 1) +OPCODE(PUSH_LOCAL_2, 0, 1) +OPCODE(PUSH_LOCAL_3, 0, 1) +OPCODE(PUSH_LOCAL_4, 0, 1) +OPCODE(PUSH_LOCAL_5, 0, 1) +OPCODE(PUSH_LOCAL_6, 0, 1) +OPCODE(PUSH_LOCAL_7, 0, 1) +OPCODE(PUSH_LOCAL_8, 0, 1) +OPCODE(PUSH_LOCAL_N, 2, 1) + +// Pop the stack top value and store to another stack local index. +// params: STORE_LOCAL_N -> 2 bytes (uint16_t) count value. +OPCODE(STORE_LOCAL_0, 0, -1) +OPCODE(STORE_LOCAL_1, 0, -1) +OPCODE(STORE_LOCAL_2, 0, -1) +OPCODE(STORE_LOCAL_3, 0, -1) +OPCODE(STORE_LOCAL_4, 0, -1) +OPCODE(STORE_LOCAL_5, 0, -1) +OPCODE(STORE_LOCAL_6, 0, -1) +OPCODE(STORE_LOCAL_7, 0, -1) +OPCODE(STORE_LOCAL_8, 0, -1) +OPCODE(STORE_LOCAL_N, 2, -1) + +// Push the script global value on the stack. +// params: 2 bytes (uint16_t) index. +OPCODE(PUSH_GLOBAL, 2, 1) + +// Pop and store the value to script's global. +// params: 2 bytes (uint16_t) index. +OPCODE(STORE_GLOBAL, 2, -1) + +// Push imported script's global value on the stack. +// params: 4 bytes script ID and 2 bytes index. +OPCODE(PUSH_GLOBAL_EXT, 6, 1) + +// Pop and store the value to imported script's global. +// params: 4 bytes script ID and 2 bytes index. +OPCODE(STORE_GLOBAL_EXT, 6, -1) + +// Push the script's function on the stack. It could later be called. But a +// function can't be stored i.e. can't assign a function with something else. +// params: 2 bytes index. +OPCODE(PUSH_FN, 2, 1) + +// Push an imported script's function. +// params: 4 bytes script ID and 2 bytes index. +OPCODE(PUSH_FN_EXT, 6, 1) + +// Pop the stack top. +OPCODE(POP, 0, -1) + +// Calls a function using stack's top N values as the arguments and once it +// done the stack top should be stored otherwise it'll be disregarded. The +// function should set the 0 th argment to return value. Locals at 0 to 8 +// marked explicitly since it's performance criticle. +// params: CALL_0..8 -> 2 bytes index. _N -> 2 bytes index and 2 bytes count. +OPCODE(CALL_0, 2, 1) //< Return value will be pushed. +OPCODE(CALL_1, 2, 0) //< 1st argument poped and return value pushed. +OPCODE(CALL_2, 2, -1) //< 2 args will be popped and return value pushed. +OPCODE(CALL_3, 2, -2) +OPCODE(CALL_4, 2, -3) +OPCODE(CALL_5, 2, -4) +OPCODE(CALL_6, 2, -5) +OPCODE(CALL_7, 2, -6) +OPCODE(CALL_8, 2, -7) +OPCODE(CALL_N, 4, -0) //< Will calculated at compile time. + +// Call a function from an imported script. +// params: 4 bytes script ID and 2 bytes index. _N -> +2 bytes for argc. +OPCODE(CALL_EXT_0, 6, 1) +OPCODE(CALL_EXT_1, 6, 0) +OPCODE(CALL_EXT_2, 6, -1) +OPCODE(CALL_EXT_3, 6, -2) +OPCODE(CALL_EXT_4, 6, -3) +OPCODE(CALL_EXT_5, 6, -4) +OPCODE(CALL_EXT_6, 6, -5) +OPCODE(CALL_EXT_7, 6, -6) +OPCODE(CALL_EXT_8, 6, -7) +OPCODE(CALL_EXT_N, 8, -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. +OPCODE(JUMP, 2, 0) + +// Pop the stack top value and if it's true jump. +// param: 2 bytes jump address. +OPCODE(JUMP_NOT, 2, -1) + +// Pop the stack top value and if it's false jump. +// param: 2 bytes jump address. +OPCODE(JUMP_IF_NOT, 2, -1) + +// Pop the stack top value and store it to the current stack frame's 0 index. +// Then it'll pop the current stack frame. +OPCODE(RETURN, 0, -1) + +// Pop var get attribute push the value. +// param: 2 byte attrib name index. +OPCODE(GET_ATTRIB, 2, 0) + +// Pop var and value update the attribute push result. +// param: 2 byte attrib name index. +OPCODE(SET_ATTRIB, 2, -1) + +// Pop var, key, get value and push the result. +OPCODE(GET_SUBSCRIPT, 0, -1) + +// Pop var, key, value set and push value back. +OPCODE(SET_SUBSCRIPT, 0, -2) + +// Pop unary operand and push value. +OPCODE(NEGATIVE, 0, 0) //< Negative number value. +OPCODE(NOT, 0, 0) //< boolean not. +OPCODE(BIT_NOT, 0, 0) //< bitwise not. + +// Pop binary operands and push value. +OPCODE(ADD, 0, -1) +OPCODE(SUBTRACT, 0, -1) +OPCODE(MULTIPLY, 0, -1) +OPCODE(DIVIDE, 0, -1) +OPCODE(MOD, 0, -1) + +OPCODE(BIT_AND, 0, -1) +OPCODE(BIT_OR, 0, -1) +OPCODE(BIT_XOR, 0, -1) +OPCODE(BIT_LSHIFT, 0, -1) +OPCODE(BIT_RSHIFT, 0, -1) + +OPCODE(AND, 0, -1) +OPCODE(OR, 0, -1) +OPCODE(EQEQ, 0, -1) +OPCODE(NOTEQ, 0, -1) +OPCODE(LT, 0, -1) +OPCODE(LTEQ, 0, -1) +OPCODE(GT, 0, -1) +OPCODE(GTEQ, 0, -1) + +OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push. +OPCODE(IS, 0, -1) +OPCODE(IN, 0, -1) + +// TODO: literal list, map + +// A sudo instruction which will never be called. A function's last opcode +// used for debugging. +OPCODE(END, 0, 0) diff --git a/src/types/buffer.template.c b/src/types/buffer.template.c index 197c91d..93537a5 100644 --- a/src/types/buffer.template.c +++ b/src/types/buffer.template.c @@ -29,6 +29,7 @@ void $name_l$BufferFill($name$Buffer* self, MSVM* vm, $type$ data, int count) { if (self->capacity < self->count + count) { int capacity = utilPowerOf2Ceil((int)self->count + count); + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; self->data = ($type$*)vmRealloc(vm, self->data, self->capacity * sizeof($type$), capacity * sizeof($type$)); self->capacity = capacity; diff --git a/src/types/buffer.template.h b/src/types/buffer.template.h index eea1f8e..4a19c58 100644 --- a/src/types/buffer.template.h +++ b/src/types/buffer.template.h @@ -13,12 +13,6 @@ #include "../common.h" #include "miniscript.h" -// The factor by which the buffer will grow when it's capacity reached. -#define GROW_FACTOR 2 - -// The initial capacity of the buffer. -#define MIN_CAPACITY 16 - // A place holder typedef to prevent IDE syntax errors. Remove this line // when generating the source. typedef uint8_t $type$; diff --git a/src/var.c b/src/var.c index 1ccde3a..031547b 100644 --- a/src/var.c +++ b/src/var.c @@ -62,6 +62,12 @@ Script* newScript(MSVM* vm) { varBufferInit(&script->globals); nameTableInit(&script->global_names); + varBufferInit(&script->literals); + + vmPushTempRef(vm, &script->_super); + script->body = newFunction(vm, "@(ScriptLevel)", script, false); + vmPopTempRef(vm); + functionBufferInit(&script->functions); nameTableInit(&script->function_names); @@ -83,9 +89,9 @@ Function* newFunction(MSVM* vm, const char* name, Script* owner, if (is_native) { func->native = NULL; } else { - vmPushTempRef(vm, &func->_super); + //vmPushTempRef(vm, &func->_super); Fn* fn = ALLOCATE(vm, Fn); - vmPopTempRef(vm); + //vmPopTempRef(vm); byteBufferInit(&fn->opcodes); intBufferInit(&fn->oplines); @@ -94,3 +100,13 @@ Function* newFunction(MSVM* vm, const char* name, Script* owner, } return func; } + + +bool isVauesSame(Var v1, Var v2) { +#if VAR_NAN_TAGGING + // Bit representation of each values are unique so just compare the bits. + return v1 == v2; +#else +#error TODO: +#endif +} \ No newline at end of file diff --git a/src/var.h b/src/var.h index 1faf0cc..98cbcd3 100644 --- a/src/var.h +++ b/src/var.h @@ -242,6 +242,10 @@ struct Script { VarBuffer globals; //< Script level global variables. NameTable global_names; //< Name map to index in globals. + VarBuffer literals; //< Script literal constant values. + + Function* body; //< Script body is an anonymous function. + FunctionBuffer functions; //< Script level functions. NameTable function_names; //< Name map to index in functions. @@ -293,7 +297,11 @@ Script* newScript(MSVM* vm); // Allocate new Function object and return Function*. Parameter [name] should // be the name in the Script's nametable. -Function* newFunction(MSVM* vm, const char* name, Script* owner, bool is_native); +Function* newFunction(MSVM* vm, const char* name, Script* owner, + bool is_native); +// Utility functions ////////////////////////////////////////////////////////// + +bool isVauesSame(Var v1, Var v2); #endif // VAR_H diff --git a/src/vm.h b/src/vm.h index 591c05c..da0887f 100644 --- a/src/vm.h +++ b/src/vm.h @@ -16,6 +16,12 @@ // garbage collected. #define MAX_TEMP_REFERENCE 8 +typedef enum { + #define OPCODE(name, _, __) OP_##name, + #include "opcodes.h" + #undef OPCODE +} Opcode; + struct MSVM { // The first object in the link list of all heap allocated objects. diff --git a/test/main.c b/test/main.c index bf3b4c0..5a2b95a 100644 --- a/test/main.c +++ b/test/main.c @@ -35,7 +35,10 @@ MSLoadScriptResult loadScript(MSVM* vm, const char* path) { MSLoadScriptResult result; result.is_failed = false; - result.source = "def someFunction(a, b, c);"; + result.source = "" + "if -1+2 * 3\n" + "end\n" + ; return result; // FIXME: @@ -60,7 +63,6 @@ int main() { //clogger_logfError("[DummyError] dummy error\n"); //clogger_logfWarning("[DummyWarning] dummy warning\n"); - printf("Here are the first 8 chars: %.8s\n", "A string that is more than 8 chars"); //parseError(parser, "A function named %.*s already exists at %s:%s", length, start, file, line); FILE* fp = fopen("test.ms", "r");