diff --git a/cli/TODO.txt b/cli/TODO.txt index cfdb481..653f5e1 100644 --- a/cli/TODO.txt +++ b/cli/TODO.txt @@ -1,6 +1,7 @@ // To implement. +[ ] Resolve function name (called before defined). [ ] Relative file import. [ ] Remove resolve path for the root module. diff --git a/src/compiler.c b/src/compiler.c index 41b98b6..a2f0f73 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -62,6 +62,7 @@ typedef enum { TK_AMP, // & TK_PIPE, // | TK_CARET, // ^ + TK_ARROW, // -> TK_PLUS, // + TK_MINUS, // - @@ -221,6 +222,7 @@ typedef enum { PREC_TERM, // + - PREC_FACTOR, // * / % PREC_UNARY, // - ! ~ + PREC_CHAIN_CALL, // -> PREC_CALL, // () PREC_SUBSCRIPT, // [] PREC_ATTRIB, // .index @@ -623,7 +625,13 @@ static void lexToken(Parser* parser) { return; case '-': - setNextTwoCharToken(parser, '=', TK_MINUS, TK_MINUSEQ); + if (matchChar(parser, '=')) { + setNextToken(parser, TK_MINUSEQ); // '-=' + } else if (matchChar(parser, '>')) { + setNextToken(parser, TK_ARROW); // '->' + } else { + setNextToken(parser, TK_MINUS); // '-' + } return; case '*': @@ -892,6 +900,7 @@ static void exprName(Compiler* compiler, bool can_assign); static void exprOr(Compiler* compiler, bool can_assign); static void exprAnd(Compiler* compiler, bool can_assign); +static void exprChainCall(Compiler* compiler, bool can_assign); static void exprBinaryOp(Compiler* compiler, bool can_assign); static void exprUnaryOp(Compiler* compiler, bool can_assign); @@ -930,6 +939,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_AMP */ { NULL, exprBinaryOp, PREC_BITWISE_AND }, /* TK_PIPE */ { NULL, exprBinaryOp, PREC_BITWISE_OR }, /* TK_CARET */ { NULL, exprBinaryOp, PREC_BITWISE_XOR }, + /* TK_ARROW */ { NULL, exprChainCall, PREC_CHAIN_CALL }, /* TK_PLUS */ { NULL, exprBinaryOp, PREC_TERM }, /* TK_MINUS */ { exprUnaryOp, exprBinaryOp, PREC_TERM }, /* TK_STAR */ { NULL, exprBinaryOp, PREC_FACTOR }, @@ -1016,7 +1026,7 @@ static void emitPushVariable(Compiler* compiler, int index, bool global) { static void exprLiteral(Compiler* compiler, bool can_assign) { Token* value = &compiler->parser.previous; int index = compilerAddConstant(compiler, value->value); - emitOpcode(compiler, OP_CONSTANT); + emitOpcode(compiler, OP_PUSH_CONSTANT); emitShort(compiler, index); } @@ -1161,6 +1171,30 @@ void exprAnd(Compiler* compiler, bool can_assign) { patchJump(compiler, end_offset); } +static void exprChainCall(Compiler* compiler, bool can_assign) { + skipNewLines(&compiler->parser); + parsePrecedence(compiler, (Precedence)(PREC_CHAIN_CALL + 1)); + emitOpcode(compiler, OP_SWAP); // Swap the data with the function. + + int argc = 1; // The initial data. + + if (match(&compiler->parser, TK_LBRACE)) { + if (!match(&compiler->parser, TK_RBRACE)) { + do { + skipNewLines(&compiler->parser); + compileExpression(compiler); + skipNewLines(&compiler->parser); + argc++; + } while (match(&compiler->parser, TK_COMMA)); + consume(&compiler->parser, TK_RBRACE, "Expected '}' after chain call" + "parameter list."); + } + } + + emitOpcode(compiler, OP_CALL); + emitShort(compiler, argc); +} + static void exprBinaryOp(Compiler* compiler, bool can_assign) { TokenType op = compiler->parser.previous.type; skipNewLines(&compiler->parser); @@ -1490,7 +1524,7 @@ static void emitOpcode(Compiler* compiler, Opcode opcode) { // make one. static void emitConstant(Compiler* compiler, Var value) { int index = compilerAddConstant(compiler, value); - emitOpcode(compiler, OP_CONSTANT); + emitOpcode(compiler, OP_PUSH_CONSTANT); emitShort(compiler, index); } @@ -1610,7 +1644,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { compilerExitBlock(compiler); // Parameter depth. #if DEBUG_DUMP_COMPILED_CODE - dumpInstructions(compiler->vm, compiler->func->ptr); + dumpFunctionCode(compiler->vm, compiler->func->ptr); #endif compiler->func = compiler->func->outer_func; diff --git a/src/core.c b/src/core.c index 280331e..a73433b 100644 --- a/src/core.c +++ b/src/core.c @@ -5,9 +5,11 @@ #include "core.h" +#include #include #include +#include "utils.h" #include "var.h" #include "vm.h" @@ -84,6 +86,20 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size, return true; } +// Check if [var] is string for argument at [arg_ind]. If not set error and +// return false. +static bool validateArgString(PKVM* vm, Var var, String** value, int arg_ind) { + if (!IS_OBJ(var) || AS_OBJ(var)->type != OBJ_STRING) { + String* str_arg = toString(vm, VAR_NUM((double)arg_ind), false); + vmPushTempRef(vm, &str_arg->_super); + vm->fiber->error = stringFormat(vm, "Expected a string at argument @.", + str_arg, false); + vmPopTempRef(vm); + } + *value = (String*)AS_OBJ(var); + return true; +} + /*****************************************************************************/ /* BUILTIN FUNCTIONS API */ /*****************************************************************************/ @@ -91,7 +107,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size, // Argument getter (1 based). #define ARG(n) vm->fiber->ret[n] -// Convinent macro +// Convinent macros. #define ARG1 ARG(1) #define ARG2 ARG(2) #define ARG3 ARG(3) @@ -216,6 +232,49 @@ void corePrint(PKVM* vm) { vm->config.write_fn(vm, "\n"); } +// string functions +// ---------------- + +void coreStrLower(PKVM* vm) { + String* str; + if (!validateArgString(vm, ARG1, &str, 1)) return; + + String* result = newStringLength(vm, str->data, str->length); + char* data = result->data; + for (; *data; ++data) *data = tolower(*data); + // Since the string is modified re-hash it. + result->hash = utilHashString(result->data); + + RET(VAR_OBJ(&result->_super)); +} + +void coreStrUpper(PKVM* vm) { + String* str; + if (!validateArgString(vm, ARG1, &str, 1)) return; + + String* result = newStringLength(vm, str->data, str->length); + char* data = result->data; + for (; *data; ++data) *data = toupper(*data); + // Since the string is modified re-hash it. + result->hash = utilHashString(result->data); + + RET(VAR_OBJ(&result->_super)); +} + +void coreStrStrip(PKVM* vm) { + String* str; + if (!validateArgString(vm, ARG1, &str, 1)) return; + + const char* start = str->data; + while (*start && isspace(*start)) start++; + if (*start == '\0') RET(VAR_OBJ(&newStringLength(vm, NULL, 0)->_super)); + + const char* end = str->data + str->length - 1; + while (isspace(*end)) end--; + + RET(VAR_OBJ(&newStringLength(vm, start, end - start + 1)->_super)); +} + /*****************************************************************************/ /* CORE LIBRARY METHODS */ /*****************************************************************************/ @@ -303,6 +362,11 @@ void initializeCore(PKVM* vm) { INITALIZE_BUILTIN_FN("to_string", coreToString, 1); INITALIZE_BUILTIN_FN("print", corePrint, -1); + // string functions. + INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1); + INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1); + INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1); + // Make STD scripts. Script* std; // A temporary pointer to the current std script. Function* fn; // A temporary pointer to the allocated function function. @@ -671,12 +735,15 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { { Var value = mapGet((Map*)obj, key); if (IS_UNDEF(value)) { + String* key_str = toString(vm, key, true); - vmPushTempRef(vm, &key_str->_super); - vm->fiber->error = stringFormat(vm, "Key (@) not exists", key_str); + if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) { + vm->fiber->error = stringFormat(vm, "Invalid key '@'.", key_str); + } else { + vm->fiber->error = stringFormat(vm, "Key '@' not exists", key_str); + } vmPopTempRef(vm); - return VAR_NULL; } return value; diff --git a/src/debug.c b/src/debug.c index 8f93e75..6f3b7a7 100644 --- a/src/debug.c +++ b/src/debug.c @@ -16,7 +16,6 @@ static const char* op_name[] = { NULL, }; - static void _dumpValue(PKVM* vm, Var value, bool recursive) { if (IS_NULL(value)) { printf("null"); @@ -53,8 +52,22 @@ static void _dumpValue(PKVM* vm, Var value, bool recursive) { } case OBJ_MAP: - TODO; + { + Map* map = (Map*)obj; + if (recursive) { + printf("{...}"); + } else { + printf("{"); + for (uint32_t i = 0; i < map->count; i++) { + if (i != 0) printf(", "); + _dumpValue(vm, map->entries[i].key, true); + printf(":"); + _dumpValue(vm, map->entries[i].value, true); + } + printf("}"); + } return; + } case OBJ_RANGE: { @@ -64,10 +77,11 @@ static void _dumpValue(PKVM* vm, Var value, bool recursive) { } case OBJ_SCRIPT: - printf("[Script:%p]", obj); + printf("[Script:%s]", ((Script*)obj)->name->data); return; + case OBJ_FUNC: - printf("[Fn:%p]", obj); + printf("[Fn:%s]", ((Function*)obj)->name); return; case OBJ_FIBER: @@ -84,8 +98,7 @@ void dumpValue(PKVM* vm, Var value) { _dumpValue(vm, value, false); } -void dumpInstructions(PKVM* vm, Function* func) { - +void dumpFunctionCode(PKVM* vm, Function* func) { uint32_t i = 0; uint8_t* opcodes = func->fn->opcodes.data; @@ -116,7 +129,7 @@ void dumpInstructions(PKVM* vm, Function* func) { Opcode op = (Opcode)func->fn->opcodes.data[i++]; switch (op) { - case OP_CONSTANT: + case OP_PUSH_CONSTANT: { int index = READ_SHORT(); printf("%5d ", index); @@ -131,6 +144,7 @@ void dumpInstructions(PKVM* vm, Function* func) { case OP_PUSH_SELF: case OP_PUSH_TRUE: case OP_PUSH_FALSE: + case OP_SWAP: NO_ARGS(); break; @@ -174,9 +188,20 @@ void dumpInstructions(PKVM* vm, Function* func) { case OP_PUSH_GLOBAL: case OP_STORE_GLOBAL: - case OP_PUSH_FN: - SHORT_ARG(); + { + int index = READ_SHORT(); + int name_index = func->owner->global_names.data[index]; + String* name = func->owner->names.data[name_index]; + printf("%5d '%s'\n", index, name->data); break; + } + + case OP_PUSH_FN: + { + int index = READ_SHORT(); + printf("%5d [Fn:%s]\n", index, func->name); + break; + } case OP_PUSH_BUILTIN_FN: { @@ -184,7 +209,6 @@ void dumpInstructions(PKVM* vm, Function* func) { printf("%5d [Fn:%s]\n", index, getBuiltinFunctionName(vm, index)); break; } - case OP_POP: NO_ARGS(); break; case OP_IMPORT: NO_ARGS(); break; @@ -237,8 +261,6 @@ void dumpInstructions(PKVM* vm, Function* func) { case OP_BIT_XOR: case OP_BIT_LSHIFT: case OP_BIT_RSHIFT: - case OP_AND: - case OP_OR: case OP_EQEQ: case OP_NOTEQ: case OP_LT: @@ -258,19 +280,18 @@ void dumpInstructions(PKVM* vm, Function* func) { } } -void reportStackTrace(PKVM* vm) { - if (vm->config.error_fn == NULL) return; - +void dumpStackFrame(PKVM* vm) { Fiber* fiber = vm->fiber; + int frame_ind = fiber->frame_count - 1; + ASSERT(frame_ind >= 0, OOPS); + CallFrame* frame = &fiber->frames[frame_ind]; + Var* sp = fiber->sp - 1; - vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data); - - for (int i = fiber->frame_count - 1; i >= 0; i--) { - CallFrame* frame = &fiber->frames[i]; - Function* fn = frame->fn; - ASSERT(!fn->is_native, OOPS); - int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; - vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->name->data, line, fn->name); + printf("Frame: %d.\n", frame_ind); + for (; sp >= frame->rbp; sp--) { + printf(" "); + dumpValue(vm, *sp); + printf("\n"); } } diff --git a/src/debug.h b/src/debug.h index f7709db..9b8b554 100644 --- a/src/debug.h +++ b/src/debug.h @@ -12,9 +12,9 @@ void dumpValue(PKVM* vm, Var value); // Dump opcodes of the given function. -void dumpInstructions(PKVM* vm, Function* func); +void dumpFunctionCode(PKVM* vm, Function* func); -// Print stack track. -void reportStackTrace(PKVM* vm); +// Dump the current (top most) stack call frame. +void dumpStackFrame(PKVM* vm); #endif // DEBUG_H diff --git a/src/opcodes.h b/src/opcodes.h index 8911e01..f2570f8 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -15,7 +15,7 @@ // Load the constant at index [arg] from the script's literals. // params: 2 byte (uint16_t) index value. -OPCODE(CONSTANT, 2, 1) +OPCODE(PUSH_CONSTANT, 2, 1) // Push null on the stack. OPCODE(PUSH_NULL, 0, 1) @@ -29,6 +29,9 @@ OPCODE(PUSH_TRUE, 0, 1) // Push false on the stack. OPCODE(PUSH_FALSE, 0, 1) +// Swap the top 2 stack values. +OPCODE(SWAP, 0, 0) + // Push a new list to construct from literal. // param: 2 bytes list size (defalt is 0). OPCODE(PUSH_LIST, 2, 1) @@ -182,8 +185,6 @@ 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) diff --git a/src/vm.c b/src/vm.c index f3d0622..526ee75 100644 --- a/src/vm.c +++ b/src/vm.c @@ -6,7 +6,7 @@ #include "vm.h" #include "core.h" -#include "debug.h" +//#include "debug.h" //< Wrap around debug macro. #include "utils.h" #define HAS_ERROR() (vm->fiber->error != NULL) @@ -375,10 +375,19 @@ void pkSetRuntimeError(PKVM* vm, const char* message) { void vmReportError(PKVM* vm) { ASSERT(HAS_ERROR(), "runtimeError() should be called after an error."); - // TODO: pass the error to the caller of the fiber. - reportStackTrace(vm); + // Print the Error message and stack trace. + if (vm->config.error_fn == NULL) return; + Fiber* fiber = vm->fiber; + vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data); + for (int i = fiber->frame_count - 1; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + Function* fn = frame->fn; + ASSERT(!fn->is_native, OOPS); + int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; + vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->name->data, line, fn->name); + } } // FIXME: temp. @@ -530,7 +539,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Opcode instruction; SWITCH(instruction) { - OPCODE(CONSTANT): + OPCODE(PUSH_CONSTANT): { uint16_t index = READ_SHORT(); ASSERT_INDEX(index, script->literals.count); @@ -550,6 +559,16 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { PUSH(VAR_FALSE); DISPATCH(); + OPCODE(SWAP): + { + Var top1 = *(vm->fiber->sp - 1); + Var top2 = *(vm->fiber->sp - 2); + + *(vm->fiber->sp - 1) = top2; + *(vm->fiber->sp - 2) = top1; + DISPATCH(); + } + OPCODE(PUSH_LIST): { List* list = newList(vm, (uint32_t)READ_SHORT()); @@ -754,7 +773,6 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { DISPATCH(); } - OPCODE(JUMP_IF): { Var cond = POP(); @@ -929,21 +947,6 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { OPCODE(BIT_XOR): OPCODE(BIT_LSHIFT): OPCODE(BIT_RSHIFT): - OPCODE(AND): - TODO; - - OPCODE(OR): - { - TODO; - // Python like or operator. - //Var v1 = POP(), v2 = POP(); - //if (toBool(v1)) { - // PUSH(v1); - //} else { - // PUSH(v2); - //} - DISPATCH(); - } OPCODE(EQEQ) : { diff --git a/test/lang/chain_call.pk b/test/lang/chain_call.pk new file mode 100644 index 0000000..ca67534 --- /dev/null +++ b/test/lang/chain_call.pk @@ -0,0 +1,27 @@ + +## Chain call tests. + +# concatenative programming + +def fn1(data) + return '[fn1:' + data + ']' +end + +def fn2(data, suffix) + return '[fn2:' + data + '|' + suffix + ']' +end + +def fn3(data) + return '[fn3:' + data + ']' +end + +result = 'data' -> fn1 -> fn2{'suff'} -> fn3 +## `result -> print` same as `print(result)` +assert(result == '[fn3:[fn2:[fn1:data]|suff]]') + +result = ' tEST+InG ' -> str_strip -> str_lower +assert(result == 'test+ing') + + + + diff --git a/test/lang/import.pk b/test/lang/import.pk index fe6d3e1..3728f7d 100644 --- a/test/lang/import.pk +++ b/test/lang/import.pk @@ -1,9 +1,9 @@ ## Testing import statement -import os -import os, path -import os as o, path as p -from os import clock -from os import clock as c +import lang +import lang, path +import lang as o, path as p +from lang import clock +from lang import clock as c from path import abspath, curdir from path import abspath as ap, curdir as cd diff --git a/test/lang/logical.pk b/test/lang/logical.pk index ed930df..056e0cc 100644 --- a/test/lang/logical.pk +++ b/test/lang/logical.pk @@ -5,8 +5,10 @@ val = 0 a = false; b = true; if a and b then assert(false) end -if a or b then va -else assert(false, '') end + +if a or b then val = 42 +else assert(false) end +assert(val == 42) get_true = func return true end diff --git a/test/run_tests.bat b/test/run_tests.bat index b8fc1eb..95355db 100644 --- a/test/run_tests.bat +++ b/test/run_tests.bat @@ -1,13 +1,14 @@ @echo off -set files=( ^ - lang\basics.pk ^ - lang\import.pk ^ - lang\if.pk ^ - lang\logical.pk ^ - ^ - examples\fib.pk ^ - examples\prime.pk ^ +set files=( ^ + lang\basics.pk ^ + lang\import.pk ^ + lang\if.pk ^ + lang\logical.pk ^ + lang\chain_call.pk ^ + ^ + examples\fib.pk ^ + examples\prime.pk ^ ) set errorlevel=0