mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +08:00
Merge pull request #29 from ThakeeNathees/testing-impl
A small test script added
This commit is contained in:
commit
82982d3ceb
@ -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.
|
||||
|
30
cli/main.c
30
cli/main.c
@ -4,12 +4,13 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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 <source_path>\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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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)
|
40
test/run.py
Normal file
40
test/run.py
Normal file
@ -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()
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user