diff --git a/cli/TODO.txt b/cli/TODO.txt index d4520f8..fea3a00 100644 --- a/cli/TODO.txt +++ b/cli/TODO.txt @@ -1,12 +1,11 @@ // To implement. - -[ ] Resolve function name (called before defined). +[ ] Implement argparse. +[ ] Implement resolve path in cli. [ ] Structs. [ ] Implement math library. -[ ] Implement resolve path in cli. [ ] Single header for embedding. [ ] Implement fiber from script body and vm run fibers (not scripts). Then remove vm's root script. diff --git a/cli/main.c b/cli/main.c index 589736e..0cbba04 100644 --- a/cli/main.c +++ b/cli/main.c @@ -4,12 +4,13 @@ */ #include +#include #include #include "pocketlang.h" void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line, - const char* message) { + const char* message) { if (type == PK_ERROR_COMPILE) { fprintf(stderr, "Error: %s\n at %s:%i\n", message, file, line); } else if (type == PK_ERROR_RUNTIME) { @@ -63,9 +64,9 @@ pkStringPtr loadScript(PKVM* vm, const char* path) { // Read source to buffer. char* buff = (char*)malloc((size_t)(file_size) + 1); - size_t read = fread(buff, sizeof(char), file_size, file); // Using read instead of file_size is because "\r\n" is read as '\n' in - // windows the '\r'. + // windows. + size_t read = fread(buff, sizeof(char), file_size, file); buff[read] = '\0'; fclose(file); @@ -83,12 +84,12 @@ int main(int argc, char** argv) { "Free and open source software under the terms of the MIT license.\n"; const char* help = "Usage: pocketlang \n"; + // TODO: implement arg parse. + if (argc < 2) { printf("%s\n%s", notice, help); return 0; } - - const char* source_path = argv[1]; pkConfiguration config = pkNewConfiguration(); config.error_fn = errorPrint; @@ -96,9 +97,20 @@ int main(int argc, char** argv) { config.load_script_fn = loadScript; config.resolve_path_fn = resolvePath; - PKVM* vm = pkNewVM(&config); - PKInterpretResult result = pkInterpret(vm, source_path); - pkFreeVM(vm); - return result; + // FIXME: this is temp till arg parse implemented. + if (argc >= 3 && strcmp(argv[1], "-c") == 0) { + PKVM* vm = pkNewVM(&config); + PKInterpretResult result = pkInterpretSource(vm, argv[2], "$(Source)"); + pkFreeVM(vm); + return result; + + } else { + PKVM* vm = pkNewVM(&config); + PKInterpretResult result = pkInterpret(vm, argv[1]); + pkFreeVM(vm); + return result; + } + + return 0; } diff --git a/src/compiler.c b/src/compiler.c index 8776ca0..7baabac 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -691,7 +691,7 @@ static void lexToken(Compiler* compiler) { eatName(compiler); } else { if (c >= 32 && c <= 126) { - lexError(compiler, "Invalid character %c", c); + lexError(compiler, "Invalid character '%c'", c); } else { lexError(compiler, "Invalid byte 0x%x", (uint8_t)c); } @@ -752,15 +752,22 @@ static bool matchLine(Compiler* compiler) { return true; } -// Match semi collon, multiple new lines or peek 'end' keyword. +// Match semi collon, multiple new lines or peek 'end', 'else', 'elif' +// keywords. static bool matchEndStatement(Compiler* compiler) { if (match(compiler, TK_SEMICOLLON)) { skipNewLines(compiler); return true; } - - if (matchLine(compiler) || peek(compiler) == TK_END || peek(compiler) == TK_EOF) + if (matchLine(compiler) || peek(compiler) == TK_EOF) return true; + + // In the below statement we don't require any new lines or semicollons. + // 'if cond then stmnt1 elif cond2 then stmnt2 else stmnt3 end' + if (peek(compiler) == TK_END || peek(compiler) == TK_ELSE || + peek(compiler) == TK_ELIF) + return true; + return false; } @@ -1207,7 +1214,7 @@ static void exprChainCall(Compiler* compiler, bool can_assign) { skipNewLines(compiler); argc++; } while (match(compiler, TK_COMMA)); - consume(compiler, TK_RBRACE, "Expected '}' after chain call" + consume(compiler, TK_RBRACE, "Expected '}' after chain call " "parameter list."); } } @@ -1263,7 +1270,7 @@ static void exprGrouping(Compiler* compiler, bool can_assign) { skipNewLines(compiler); compileExpression(compiler); skipNewLines(compiler); - consume(compiler, TK_RPARAN, "Expected ')' after expression "); + consume(compiler, TK_RPARAN, "Expected ')' after expression."); } static void exprList(Compiler* compiler, bool can_assign) { @@ -1699,7 +1706,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { } } if (predefined) - parseError(compiler, "Multiple definition of a parameter"); + parseError(compiler, "Multiple definition of a parameter."); compilerAddVariable(compiler, param_name, param_len, compiler->previous.line); @@ -1783,6 +1790,11 @@ static Script* importFile(Compiler* compiler, const char* path) { resolved = vm->config.resolve_path_fn(vm, compiler->script->path->data, path); } + if (resolved.string == NULL) { + parseError(compiler, "Cannot resolve path '%s' from '%s'", path, + compiler->script->path->data); + } + // Create new string for the resolved path. And free the resolved path. int index = (int)scriptAddName(compiler->script, compiler->vm, resolved.string, (uint32_t)strlen(resolved.string)); @@ -1952,7 +1964,8 @@ L_import_done: // from module import symbol [as alias [, symbol2 [as alias]]] static void compileFromImport(Compiler* compiler) { - // Import the library and push it on the stack. + // Import the library and push it on the stack. If the import failed + // lib_from would be NULL. Script* lib_from = compilerImport(compiler); // At this point the script would be on the stack before executing the next @@ -1961,7 +1974,7 @@ static void compileFromImport(Compiler* compiler) { if (match(compiler, TK_STAR)) { // from math import * - compilerImportAll(compiler, lib_from); + if (lib_from) compilerImportAll(compiler, lib_from); } else { do { @@ -2010,7 +2023,10 @@ static void compileFromImport(Compiler* compiler) { static void compileRegularImport(Compiler* compiler) { do { - // Import the library and push it on the stack. + + // Import the library and push it on the stack. If it cannot import + // the lib would be null, but we're not terminating here, just continue + // parsing for cascaded errors. Script* lib = compilerImport(compiler); // variable to bind the imported script. @@ -2034,7 +2050,7 @@ static void compileRegularImport(Compiler* compiler) { // If it has a module name use it as binding variable. // Core libs names are it's module name but for local libs it's optional // to define a module name for a script. - if (lib->moudle != NULL) { + if (lib && lib->moudle != NULL) { // Get the variable to bind the imported symbol, if we already have a // variable with that name override it, otherwise use a new variable. @@ -2057,7 +2073,7 @@ static void compileRegularImport(Compiler* compiler) { emitOpcode(compiler, OP_POP); } else { - compilerImportAll(compiler, lib); + if (lib) compilerImportAll(compiler, lib); // Done importing everything from lib now pop the lib. emitOpcode(compiler, OP_POP); } diff --git a/test/lang/basics.pk b/test/lang/basics.pk index 139f63b..69f706e 100644 --- a/test/lang/basics.pk +++ b/test/lang/basics.pk @@ -15,3 +15,11 @@ l1[1] = true; assert(l1[1], 'List subscript set failed.') h1 = hash("testing"); h2 = hash("test" + "ing") assert(h1 == h2, 'Hash function failed.') assert(to_string(42) == '42', 'to_string() failed.') + + +## Logical statement test +val = 0; a = false; b = true +get_true = func return true end +if a and b then assert(false) end +if a or b then val = 42 else assert(false) end assert(val == 42) +if get_true() or false then val = 12 end assert(val == 12) diff --git a/test/lang/chain_call.pk b/test/lang/chain_call.pk deleted file mode 100644 index ca67534..0000000 --- a/test/lang/chain_call.pk +++ /dev/null @@ -1,27 +0,0 @@ - -## 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/functions.pk b/test/lang/functions.pk index 2b8019b..b5f1a1f 100644 --- a/test/lang/functions.pk +++ b/test/lang/functions.pk @@ -1,21 +1,26 @@ -# Function Tests. (TODO: add more) +## Function Tests. -def f1 return 'f1' end -assert(f1() == 'f1') - -def f2() return 'f2' end -assert(f2() == 'f2') - -def f3(a, b, c, d) - return c -end +def f1 return 'f1' end assert(f1() == 'f1') +def f2() return 'f2' end assert(f2() == 'f2') +def f3(a, b, c, d) return c end assert(f3('a', 'b', 'c', 'd') == 'c') -# forward call. +## Forward call. val = before_defn() def before_defn() return 'defined after the call' end - assert(val == 'defined after the call') + +## 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 +assert(result == '[fn3:[fn2:[fn1:data]|suff]]') + +result = ' tEST+InG ' -> str_strip -> str_lower +assert(result == 'test+ing') diff --git a/test/lang/if.pk b/test/lang/if.pk index be3dbb9..32e7d01 100644 --- a/test/lang/if.pk +++ b/test/lang/if.pk @@ -3,8 +3,7 @@ variable = null ## Will be changed by the control flow. unreachable = func assert(false, 'Unreachable') end -if true then variable = 42 -else unreachable() end +if true then variable = 42 else unreachable() end assert(variable == 42, 'If statement failed.') if false then unreachable() diff --git a/test/lang/logical.pk b/test/lang/logical.pk deleted file mode 100644 index 056e0cc..0000000 --- a/test/lang/logical.pk +++ /dev/null @@ -1,18 +0,0 @@ - -## Logical statement test - -val = 0 - -a = false; b = true; -if a and b then assert(false) end - -if a or b then val = 42 -else assert(false) end -assert(val == 42) - -get_true = func return true end - -if get_true() or false - val = 12 -end -assert(val == 12) diff --git a/test/run.py b/test/run.py new file mode 100644 index 0000000..c5d03fa --- /dev/null +++ b/test/run.py @@ -0,0 +1,40 @@ +import subprocess +import json, os + +files = [ + "lang/basics.pk", + "lang/functions.pk", + "lang/if.pk", + "examples/fib.pk", + "examples/prime.pk", +] + +FMT_PATH = "%-25s" +INDENTATION = ' | ' + + +def run_all_tests(): + for path in files: + run_file(path) + +def run_file(path): + print(FMT_PATH % path, end='') + result = run_command(['pocket', path]) + if result.returncode != 0: + print('-- Failed') + err = INDENTATION + result.stderr \ + .decode('utf8') \ + .replace('\n', '\n' + INDENTATION) + print(err) + else: + print('-- OK') + + +def run_command(command): + return subprocess.run(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + +run_all_tests() + diff --git a/test/run_tests.bat b/test/run_tests.bat deleted file mode 100644 index 358127c..0000000 --- a/test/run_tests.bat +++ /dev/null @@ -1,39 +0,0 @@ -@echo off - -:: Resolve path hasn't implemented. -cd lang -echo Testing "import.pk" -pocket import.pk -if %errorlevel% neq 0 goto :FAILED -cd .. - -set files=( ^ - lang\basics.pk ^ - lang\if.pk ^ - lang\logical.pk ^ - lang\chain_call.pk ^ - ^ - examples\fib.pk ^ - examples\prime.pk ^ -) - -set errorlevel=0 - -for %%f in %files% do ( - echo Testing %%f - pocket %%f - if %errorlevel% neq 0 goto :FAILED -) -goto :SUCCESS - -:FAILED -echo. -echo Test failed. -goto :END - -:SUCCESS -echo. -echo All tests were passed. -goto :END - -:END