diff --git a/README.md b/README.md index ae2f33d..efe89c7 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -# MiniScript \ No newline at end of file +## MiniScript Language + +MiniScript is a simple embeddable, functional, dynamic-typed, bytecode-interpreted, scripting language written in C. It uses the [mark-and-sweep](https://en.wikipedia.org/wiki/Tracing_garbage_collection) method for garbage collection. MiniScript is is syntactically similar to Ruby. The frontend and expression parsing techniques were written using [Wren Language](https://wren.io/) and their wonderful book [craftinginterpreters](http://www.craftinginterpreters.com/) as a reference. + +### What MiniScript looks like + +```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]) + end + return ret +end + +## Return an array where each element returns true with function [fn] and +## belongs to [arr]. +def filter(arr, fn) + ret = [] + for elem in arr + if fn(elem) + array_append(ret, elem) + end + end + return ret +end + +array = [42, null, 3.14, "String", 0..10, [100]] +nums = filter(array, is_num) +print(get_max(nums)) + +``` + diff --git a/src/compiler.c b/src/compiler.c index 3f4447d..0cd95ac 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -87,7 +87,6 @@ typedef enum { //TK_XOREQ, // ^= // Keywords. - TK_IMPORT, // import TK_DEF, // def TK_NATIVE, // native (C function declaration) TK_END, // end @@ -146,7 +145,6 @@ typedef struct { // List of keywords mapped into their identifiers. static _Keyword _keywords[] = { - { "import", 6, TK_IMPORT }, { "def", 3, TK_DEF }, { "native", 6, TK_NATIVE }, { "end", 3, TK_END }, @@ -719,13 +717,6 @@ typedef struct { NameDefnType type; - // Could be found in one of the imported script or in it's imported script - // recursively. If true [_extern] will be the script ID. - bool is_extern; - - // Extern script's index. - int _extern; - // Index in the variable/function buffer/array. int index; @@ -740,7 +731,6 @@ static NameSearchResult compilerSearchName(Compiler* compiler, NameSearchResult result; result.type = NAME_NOT_DEFINED; - result.is_extern = false; // Search through builtin functions. int index = findBuiltinFunction(name, length); @@ -781,8 +771,6 @@ static NameSearchResult compilerSearchName(Compiler* compiler, return result; } - // TODO: search in imported scripts. - return result; } @@ -863,7 +851,6 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_DIVEQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT /* TK_SRIGHT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT }, /* TK_SLEFT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT }, - /* TK_IMPORT */ NO_RULE, /* TK_DEF */ NO_RULE, /* TK_EXTERN */ NO_RULE, /* TK_END */ NO_RULE, @@ -967,34 +954,17 @@ static void exprName(Compiler* compiler, bool can_assign) { case NAME_GLOBAL_VAR: if (can_assign && match(&compiler->parser, TK_EQ)) { compileExpression(compiler); - if (result.is_extern) { - emitOpcode(compiler, OP_STORE_GLOBAL_EXT); - emitShort(compiler, result._extern); - emitShort(compiler, result.index); - } else { - _emitStoreVariable(compiler, result.index, true); - } + _emitStoreVariable(compiler, result.index, true); + } else { - if (result.is_extern) { - emitOpcode(compiler, OP_PUSH_GLOBAL_EXT); - emitShort(compiler, result._extern); - emitShort(compiler, result.index); - } else { - emitOpcode(compiler, OP_PUSH_GLOBAL); - emitShort(compiler, result.index); - } + emitOpcode(compiler, OP_PUSH_GLOBAL); + emitShort(compiler, result.index); } return; case NAME_FUNCTION: - if (result.is_extern) { - emitOpcode(compiler, OP_PUSH_FN_EXT); - emitShort(compiler, result._extern); - emitShort(compiler, result.index); - } else { - emitOpcode(compiler, OP_PUSH_FN); - emitShort(compiler, result.index); - } + emitOpcode(compiler, OP_PUSH_FN); + emitShort(compiler, result.index); return; case NAME_BUILTIN: @@ -1329,6 +1299,10 @@ static void compileFunction(Compiler* compiler, bool is_native) { consume(parser, TK_END, "Expected 'end' after function definition end."); + emitOpcode(compiler, OP_PUSH_NULL); + emitOpcode(compiler, OP_RETURN); + emitOpcode(compiler, OP_END); + compilerExitBlock(compiler); // Parameter depth. compiler->function = compiler->script->body; } @@ -1511,9 +1485,6 @@ Script* compileSource(MSVM* vm, const char* path) { } else if (match(parser, TK_DEF)) { compileFunction(&compiler, false); - } else if (match(parser, TK_IMPORT)) { - // TODO: import statement must be first of all other. - TODO; } else { compileStatement(&compiler); } diff --git a/src/core.c b/src/core.c index 8f5722e..706f09d 100644 --- a/src/core.c +++ b/src/core.c @@ -13,7 +13,7 @@ typedef struct { } _BuiltinFn; // Count of builtin function +1 for termination. -#define BUILTIN_COUNT 10 +#define BUILTIN_COUNT 50 // Array of all builtin functions. _BuiltinFn builtins[BUILTIN_COUNT]; @@ -47,6 +47,33 @@ Function* getBuiltinFunction(int index) { return &builtins[index].fn; } +#define FN_IS_PRIMITE_TYPE(name, check) \ + void coreIs##name(MSVM* vm) { \ + vm->rbp[0] = VAR_BOOL(check(vm->rbp[1])); \ + } + +#define FN_IS_OBJ_TYPE(name, _enum) \ + void coreIs##name(MSVM* vm) { \ + Var arg1 = vm->rbp[1]; \ + if (IS_OBJ(arg1) && AS_OBJ(arg1)->type == _enum) { \ + vm->rbp[0] = VAR_TRUE; \ + } else { \ + vm->rbp[0] = VAR_FALSE; \ + } \ + } + +FN_IS_PRIMITE_TYPE(Null, IS_NULL) +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(Map, OBJ_MAP) +FN_IS_OBJ_TYPE(Range, OBJ_RANGE) +FN_IS_OBJ_TYPE(Function, OBJ_FUNC) +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); @@ -66,12 +93,35 @@ void corePrint(MSVM* vm) { vm->config.write_fn(vm, "\n"); } +void coreImport(MSVM* vm) { + Var arg1 = vm->rbp[1]; + if (IS_OBJ(arg1) && AS_OBJ(arg1)->type == OBJ_STRING) { + TODO; + } else { + msSetRuntimeError(vm, "Expected a String argument."); + } +} + void initializeCore(MSVM* vm) { + int i = 0; //< Iterate through builtins. + // Initialize builtin functions. - int i = 0; - initializeBuiltinFN(vm, &builtins[i++], "print", 1, corePrint); + 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_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); // Sentinal to mark the end of the array. initializeBuiltinFN(vm, &builtins[i], NULL, 0, NULL); @@ -112,11 +162,12 @@ Var varAdd(MSVM* vm, Var v1, Var v2) { return VAR_NULL; } - // TODO: string addition/ array addition etc. + TODO; //string addition/ array addition etc. return VAR_NULL; } Var varSubtract(MSVM* vm, Var v1, Var v2) { + TODO; return VAR_NULL; } @@ -130,9 +181,16 @@ Var varMultiply(MSVM* vm, Var v1, Var v2) { return VAR_NULL; } + TODO; return VAR_NULL; } Var varDivide(MSVM* vm, Var v1, Var v2) { + TODO; return VAR_NULL; +} + +bool varIterate(MSVM* vm, Var seq, Var* iterator, Var* value) { + TODO; + return false; } \ No newline at end of file diff --git a/src/core.h b/src/core.h index 39fab61..c347e24 100644 --- a/src/core.h +++ b/src/core.h @@ -24,5 +24,11 @@ Var varSubtract(MSVM* vm, Var v1, Var v2); Var varMultiply(MSVM* vm, Var v1, Var v2); Var varDivide(MSVM* vm, Var v1, Var v2); +// Functions ////////////////////////////////////////////////////////////////// + +// Parameter [iterator] should be VAR_NULL before starting the iteration. +// If an element is obtained by iteration it'll return true otherwise returns +// false indicating that the iteration is over. +bool varIterate(MSVM* vm, Var seq, Var* iterator, Var* value); #endif // CORE_H diff --git a/src/opcodes.h b/src/opcodes.h index a1ff608..960482d 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -64,23 +64,11 @@ OPCODE(PUSH_GLOBAL, 2, 1) // params: 2 bytes (uint16_t) index. OPCODE(STORE_GLOBAL, 2, -1) -// Push imported script's global value on the stack. -// params: 2 bytes script index and 2 bytes index. -OPCODE(PUSH_GLOBAL_EXT, 4, 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: 2 bytes script index and 2 bytes index. -OPCODE(PUSH_FN_EXT, 4, 1) - // Push a built in function. // params: 2 bytes index of the script. OPCODE(PUSH_BUILTIN_FN, 2, 1) @@ -106,22 +94,6 @@ OPCODE(POP, 0, -1) //OPCODE(CALL_8, 2, -8) OPCODE(CALL, 4, -0) //< Will calculated at compile time. -// A function pointer will be on top of the stack and it'll be called by OP_CALL... -// and doesn't need to specify opcode for extern or not. -// -// Call a function from an imported script. -// params: 2 bytes script ID and 2 bytes index. _N -> +2 bytes for argc. -//OPCODE(CALL_EXT_0, 4, 0) -//OPCODE(CALL_EXT_1, 4, -1) -//OPCODE(CALL_EXT_2, 4, -2) -//OPCODE(CALL_EXT_3, 4, -3) -//OPCODE(CALL_EXT_4, 4, -4) -//OPCODE(CALL_EXT_5, 4, -5) -//OPCODE(CALL_EXT_6, 4, -6) -//OPCODE(CALL_EXT_7, 4, -7) -//OPCODE(CALL_EXT_8, 4, -8) -//OPCODE(CALL_EXT_N, 6, -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. diff --git a/src/var.c b/src/var.c index 8640521..e3ff776 100644 --- a/src/var.c +++ b/src/var.c @@ -58,6 +58,14 @@ String* newString(MSVM* vm, const char* text, uint32_t length) { return string; } +Range* newRange(MSVM* vm, double from, double to) { + Range* range = ALLOCATE(vm, Range); + varInitObject(&range->_super, vm, OBJ_RANGE); + range->from = from; + range->to = to; + return range; +} + Script* newScript(MSVM* vm) { Script* script = ALLOCATE(vm, Script); varInitObject(&script->_super, vm, OBJ_SCRIPT); @@ -142,13 +150,20 @@ String* toString(MSVM* vm, Var v) { return newString(vm, ((String*)obj)->data, ((String*)obj)->length); break; - case OBJ_ARRAY: TODO; - case OBJ_MAP: TODO; - case OBJ_RANGE: TODO; - case OBJ_SCRIPT: TODO; - case OBJ_FUNC: TODO; - case OBJ_INSTANCE: TODO; - case OBJ_USER: TODO; + case OBJ_ARRAY: 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; + case OBJ_FUNC: { + const char* name = ((Function*)obj)->name; + int length = (int)strlen(name); // TODO: Assert length. + char buff[TO_STRING_BUFF_SIZE]; + memcpy(buff, "[func:", 6); + memcpy(buff + 6, name, length); + buff[6 + length] = ']'; + return newString(vm, buff, 6 + length + 1); + } + case OBJ_USER: return newString(vm, "[UserObj]", 9); // TODO; break; } diff --git a/src/var.h b/src/var.h index 357c044..ae56131 100644 --- a/src/var.h +++ b/src/var.h @@ -195,7 +195,6 @@ typedef enum /* ObjectType */ { OBJ_SCRIPT, OBJ_FUNC, - OBJ_INSTANCE, OBJ_USER, } ObjectType; @@ -281,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 Range object and return Range*. +Range* newRange(MSVM* vm, double from, double to); + // Allocate new Script object and return Script*. Script* newScript(MSVM* vm); diff --git a/src/vm.c b/src/vm.c index 73fefc6..3421d44 100644 --- a/src/vm.c +++ b/src/vm.c @@ -61,8 +61,7 @@ void vmPopTempRef(MSVM* self) { void _printStackTop(MSVM* vm) { if (vm->sp != vm->stack) { Var v = *(vm->sp - 1); - double n = AS_NUM(v); - printf("%f\n", n); + printf("%s\n", toString(vm, v)->data); } } @@ -94,7 +93,6 @@ static inline void pushCallFrame(MSVM* vm, Function* fn) { frame->ip = fn->fn->opcodes.data; } - void msSetRuntimeError(MSVM* vm, const char* format, ...) { vm->error = newString(vm, "TODO:", 5); TODO; @@ -168,7 +166,7 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { #error "OPCODE" should not be deifined here. #endif -#define DEBUG_INSTRUCTION() +#define DEBUG_INSTRUCTION() //_printStackTop(vm) #define SWITCH(code) \ L_vm_main_loop: \ @@ -231,7 +229,6 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { PUSH(rbp[index]); DISPATCH(); } - OPCODE(PUSH_LOCAL_N): { int index = READ_SHORT(); @@ -248,8 +245,17 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { OPCODE(STORE_LOCAL_6): OPCODE(STORE_LOCAL_7): OPCODE(STORE_LOCAL_8): + { + int index = (int)(instruction - OP_STORE_LOCAL_0); + rbp[index] = POP(); + DISPATCH(); + } OPCODE(STORE_LOCAL_N): - TODO; + { + int index = READ_SHORT(); + rbp[index] = POP(); + DISPATCH(); + } OPCODE(PUSH_GLOBAL): { @@ -267,10 +273,6 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { DISPATCH(); } - OPCODE(PUSH_GLOBAL_EXT): - OPCODE(STORE_GLOBAL_EXT): - TODO; - OPCODE(PUSH_FN): { int index = READ_SHORT(); @@ -280,9 +282,6 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { DISPATCH(); } - OPCODE(PUSH_FN_EXT) : - TODO; - OPCODE(PUSH_BUILTIN_FN): { Function* fn = getBuiltinFunction(READ_SHORT()); @@ -298,7 +297,6 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { { int argc = READ_SHORT(); Var* callable = vm->sp - argc - 1; - vm->rbp = callable; //< Next call frame starts here. if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) { @@ -311,8 +309,13 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { // initialized with VAR_NULL as return value. *callable = VAR_NULL; + // Next call frame starts here. (including return value). + vm->rbp = callable; + if (fn->is_native) { fn->native(vm); + // Pop function arguments except for the return value. + vm->sp = vm->rbp + 1; CHECK_ERROR(); } else { @@ -330,7 +333,30 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { OPCODE(JUMP): OPCODE(JUMP_NOT): OPCODE(JUMP_IF_NOT): + TODO; + OPCODE(RETURN): + { + Var ret = POP(); + vm->frame_count--; + + // If no more call frames. We're done. + if (vm->frame_count == 0) { + vm->sp = vm->stack; + PUSH(ret); + return RESULT_SUCCESS; + } + + // Set the return value. + *(frame->rbp - 1) = ret; + + // Pop the locals and update stack pointer. + vm->sp = frame->rbp; + + LOAD_FRAME(); + DISPATCH(); + } + OPCODE(GET_ATTRIB): OPCODE(SET_ATTRIB): OPCODE(GET_SUBSCRIPT): @@ -370,9 +396,22 @@ MSInterpretResult vmRunScript(MSVM* vm, Script* _script) { OPCODE(LTEQ): OPCODE(GT): OPCODE(GTEQ): + TODO; + OPCODE(RANGE): + { + Var to = POP(); + Var from = POP(); + if (!IS_NUM(from) || !IS_NUM(to)) { + RUNTIME_ERROR("Range arguments must be number."); + } + PUSH(VAR_OBJ(newRange(vm, AS_NUM(from), AS_NUM(to)))); + DISPATCH(); + } + OPCODE(IN): OPCODE(END): + TODO; break; default: diff --git a/test/main.c b/test/main.c index 97edaf9..d1c5ee3 100644 --- a/test/main.c +++ b/test/main.c @@ -12,13 +12,6 @@ #include -// FIXME: -//#include "../src/common.h" -//#include "../src/var.h" -//#include "../src/vm.h" -//#include "../src/types/gen/string_buffer.h" -//#include "../src/types/gen/byte_buffer.h" - static const char* opnames[] = { #define OPCODE(name, params, stack) #name, #include "../src/opcodes.h"