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.
|
// To implement.
|
||||||
|
|
||||||
|
[ ] Implement argparse.
|
||||||
[ ] Resolve function name (called before defined).
|
[ ] Implement resolve path in cli.
|
||||||
|
|
||||||
[ ] Structs.
|
[ ] Structs.
|
||||||
[ ] Implement math library.
|
[ ] Implement math library.
|
||||||
[ ] Implement resolve path in cli.
|
|
||||||
[ ] Single header for embedding.
|
[ ] Single header for embedding.
|
||||||
[ ] Implement fiber from script body and vm run fibers (not scripts).
|
[ ] Implement fiber from script body and vm run fibers (not scripts).
|
||||||
Then remove vm's root script.
|
Then remove vm's root script.
|
||||||
|
30
cli/main.c
30
cli/main.c
@ -4,12 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "pocketlang.h"
|
#include "pocketlang.h"
|
||||||
|
|
||||||
void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line,
|
void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line,
|
||||||
const char* message) {
|
const char* message) {
|
||||||
if (type == PK_ERROR_COMPILE) {
|
if (type == PK_ERROR_COMPILE) {
|
||||||
fprintf(stderr, "Error: %s\n at %s:%i\n", message, file, line);
|
fprintf(stderr, "Error: %s\n at %s:%i\n", message, file, line);
|
||||||
} else if (type == PK_ERROR_RUNTIME) {
|
} else if (type == PK_ERROR_RUNTIME) {
|
||||||
@ -63,9 +64,9 @@ pkStringPtr loadScript(PKVM* vm, const char* path) {
|
|||||||
|
|
||||||
// Read source to buffer.
|
// Read source to buffer.
|
||||||
char* buff = (char*)malloc((size_t)(file_size) + 1);
|
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
|
// 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';
|
buff[read] = '\0';
|
||||||
fclose(file);
|
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";
|
"Free and open source software under the terms of the MIT license.\n";
|
||||||
const char* help = "Usage: pocketlang <source_path>\n";
|
const char* help = "Usage: pocketlang <source_path>\n";
|
||||||
|
|
||||||
|
// TODO: implement arg parse.
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
printf("%s\n%s", notice, help);
|
printf("%s\n%s", notice, help);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* source_path = argv[1];
|
|
||||||
|
|
||||||
pkConfiguration config = pkNewConfiguration();
|
pkConfiguration config = pkNewConfiguration();
|
||||||
config.error_fn = errorPrint;
|
config.error_fn = errorPrint;
|
||||||
@ -96,9 +97,20 @@ int main(int argc, char** argv) {
|
|||||||
config.load_script_fn = loadScript;
|
config.load_script_fn = loadScript;
|
||||||
config.resolve_path_fn = resolvePath;
|
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);
|
eatName(compiler);
|
||||||
} else {
|
} else {
|
||||||
if (c >= 32 && c <= 126) {
|
if (c >= 32 && c <= 126) {
|
||||||
lexError(compiler, "Invalid character %c", c);
|
lexError(compiler, "Invalid character '%c'", c);
|
||||||
} else {
|
} else {
|
||||||
lexError(compiler, "Invalid byte 0x%x", (uint8_t)c);
|
lexError(compiler, "Invalid byte 0x%x", (uint8_t)c);
|
||||||
}
|
}
|
||||||
@ -752,15 +752,22 @@ static bool matchLine(Compiler* compiler) {
|
|||||||
return true;
|
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) {
|
static bool matchEndStatement(Compiler* compiler) {
|
||||||
if (match(compiler, TK_SEMICOLLON)) {
|
if (match(compiler, TK_SEMICOLLON)) {
|
||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (matchLine(compiler) || peek(compiler) == TK_EOF)
|
||||||
if (matchLine(compiler) || peek(compiler) == TK_END || peek(compiler) == TK_EOF)
|
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1207,7 +1214,7 @@ static void exprChainCall(Compiler* compiler, bool can_assign) {
|
|||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
argc++;
|
argc++;
|
||||||
} while (match(compiler, TK_COMMA));
|
} while (match(compiler, TK_COMMA));
|
||||||
consume(compiler, TK_RBRACE, "Expected '}' after chain call"
|
consume(compiler, TK_RBRACE, "Expected '}' after chain call "
|
||||||
"parameter list.");
|
"parameter list.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1263,7 +1270,7 @@ static void exprGrouping(Compiler* compiler, bool can_assign) {
|
|||||||
skipNewLines(compiler);
|
skipNewLines(compiler);
|
||||||
compileExpression(compiler);
|
compileExpression(compiler);
|
||||||
skipNewLines(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) {
|
static void exprList(Compiler* compiler, bool can_assign) {
|
||||||
@ -1699,7 +1706,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (predefined)
|
if (predefined)
|
||||||
parseError(compiler, "Multiple definition of a parameter");
|
parseError(compiler, "Multiple definition of a parameter.");
|
||||||
|
|
||||||
compilerAddVariable(compiler, param_name, param_len,
|
compilerAddVariable(compiler, param_name, param_len,
|
||||||
compiler->previous.line);
|
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);
|
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.
|
// Create new string for the resolved path. And free the resolved path.
|
||||||
int index = (int)scriptAddName(compiler->script, compiler->vm,
|
int index = (int)scriptAddName(compiler->script, compiler->vm,
|
||||||
resolved.string, (uint32_t)strlen(resolved.string));
|
resolved.string, (uint32_t)strlen(resolved.string));
|
||||||
@ -1952,7 +1964,8 @@ L_import_done:
|
|||||||
// from module import symbol [as alias [, symbol2 [as alias]]]
|
// from module import symbol [as alias [, symbol2 [as alias]]]
|
||||||
static void compileFromImport(Compiler* compiler) {
|
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);
|
Script* lib_from = compilerImport(compiler);
|
||||||
|
|
||||||
// At this point the script would be on the stack before executing the next
|
// 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)) {
|
if (match(compiler, TK_STAR)) {
|
||||||
// from math import *
|
// from math import *
|
||||||
compilerImportAll(compiler, lib_from);
|
if (lib_from) compilerImportAll(compiler, lib_from);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
@ -2010,7 +2023,10 @@ static void compileFromImport(Compiler* compiler) {
|
|||||||
|
|
||||||
static void compileRegularImport(Compiler* compiler) {
|
static void compileRegularImport(Compiler* compiler) {
|
||||||
do {
|
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);
|
Script* lib = compilerImport(compiler);
|
||||||
|
|
||||||
// variable to bind the imported script.
|
// 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.
|
// 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
|
// Core libs names are it's module name but for local libs it's optional
|
||||||
// to define a module name for a script.
|
// 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
|
// Get the variable to bind the imported symbol, if we already have a
|
||||||
// variable with that name override it, otherwise use a new variable.
|
// variable with that name override it, otherwise use a new variable.
|
||||||
@ -2057,7 +2073,7 @@ static void compileRegularImport(Compiler* compiler) {
|
|||||||
emitOpcode(compiler, OP_POP);
|
emitOpcode(compiler, OP_POP);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
compilerImportAll(compiler, lib);
|
if (lib) compilerImportAll(compiler, lib);
|
||||||
// Done importing everything from lib now pop the lib.
|
// Done importing everything from lib now pop the lib.
|
||||||
emitOpcode(compiler, OP_POP);
|
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")
|
h1 = hash("testing"); h2 = hash("test" + "ing")
|
||||||
assert(h1 == h2, 'Hash function failed.')
|
assert(h1 == h2, 'Hash function failed.')
|
||||||
assert(to_string(42) == '42', 'to_string() 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
|
def f1 return 'f1' end assert(f1() == 'f1')
|
||||||
assert(f1() == 'f1')
|
def f2() return 'f2' end assert(f2() == 'f2')
|
||||||
|
def f3(a, b, c, d) return c end
|
||||||
def f2() return 'f2' end
|
|
||||||
assert(f2() == 'f2')
|
|
||||||
|
|
||||||
def f3(a, b, c, d)
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
assert(f3('a', 'b', 'c', 'd') == 'c')
|
assert(f3('a', 'b', 'c', 'd') == 'c')
|
||||||
|
|
||||||
# forward call.
|
## Forward call.
|
||||||
val = before_defn()
|
val = before_defn()
|
||||||
def before_defn()
|
def before_defn()
|
||||||
return 'defined after the call'
|
return 'defined after the call'
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(val == 'defined after the call')
|
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.
|
variable = null ## Will be changed by the control flow.
|
||||||
unreachable = func assert(false, 'Unreachable') end
|
unreachable = func assert(false, 'Unreachable') end
|
||||||
|
|
||||||
if true then variable = 42
|
if true then variable = 42 else unreachable() end
|
||||||
else unreachable() end
|
|
||||||
assert(variable == 42, 'If statement failed.')
|
assert(variable == 42, 'If statement failed.')
|
||||||
|
|
||||||
if false then unreachable()
|
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