create an nds testing tool

This commit is contained in:
prod2 2022-02-05 02:45:29 +01:00
parent ef0775b252
commit 9fdbb1dda6
15 changed files with 292 additions and 58 deletions

View File

@ -1,6 +1,6 @@
var fib = funct(n) var fib = funct(n)
if (n < 2) :result = 1.5 if (n < 2) :result = 1
else :result = fib(n-1) + fib(n-2) else :result = fib(n-1) + fib(n-2)
; ;
print fib(37.2); print fib(37);

View File

@ -1,11 +1,6 @@
{ var main = funct() {
// source // source
var src = ">++++++++++>+>+[ var src = ">++++++++++[<++++++++++>-]<->>>>>+++[>+++>+++<<-]<<<<+<[>[>+>+<<-]>>[-<<+>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]>>>>>>[[-]<++++++++++<->>]<-[>+>+<<-]>[<+>-]+>[[-]<->]<<<<<<<<<->>]<[>+>+<<-]>>[-<<+>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+>+<<<-]>>>[-<<<+>>>]++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<<[>>+>+<<<-]>>>[-<<<+>>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<[[-]>>>++++++++[>>++++++<<-]>[<++++++++[>++++++<-]>.<++++++++[>------<-]>[<<+>>-]]>.<<++++++++[>>------<<-]<[->>+<<]<++++++++[<++++>-]<.>+++++++[>+++++++++<-]>+++.<+++++[>+++++++++<-]>.+++++..--------.-------.++++++++++++++>>[>>>+>+<<<<-]>>>>[-<<<<+>>>>]>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<<[>>>+>+<<<<-]>>>>[-<<<<+>>>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+<<[-]]>[>+<[-]]++>>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<+<[[-]>-<]>[<<<<<<<.>>>>>>>[-]]<<<<<<<<<.>>----.---------.<<.>>----.+++..+++++++++++++.[-]<<[-]]<[>+>+<<-]>>[-<<+>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+>+<<<-]>>>[-<<<+>>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<[[-]>++++++++[<++++>-]<.>++++++++++[>+++++++++++<-]>+.-.<<.>>++++++.------------.---.<<.>++++++[>+++<-]>.<++++++[>----<-]>++.+++++++++++..[-]<<[-]++++++++++.[-]]<[>+>+<<-]>>[-<<+>>]+++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]++++++++++. >+++++++++[>+++++++++<-]>+++.+++++++++++++.++++++++++.------.<++++++++[>>++++<<-]>>.<++++++++++.-.---------.>.<-.+++++++++++.++++++++.---------.>.<-------------.+++++++++++++.----------.>.<++++++++++++.---------------.<+++[>++++++<-]>..>.<----------.+++++++++++.>.<<+++[>------<-]>-.+++++++++++++++++.---.++++++.-------.----------.[-]>[-]<<<.[-]]<[>+>+<<-]>>[-<<+>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]++++++++++.[-]<[-]>]<+<]";
[+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[
[-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-
[>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>>
]<<<
]";
var pos = 0; var pos = 0;
var len = #src; var len = #src;
var char; var char;
@ -20,8 +15,6 @@
var ptr = 0; var ptr = 0;
var ptrmax = 30000 - 1; var ptrmax = 30000 - 1;
var i = 0; var i = 0;
while (i < ptrmax) { while (i < ptrmax) {
tape[i] = 0; tape[i] = 0;
@ -61,7 +54,7 @@
; ;
if (char == ".") if (char == ".")
print(chr(tape[ptr])) write(chr(tape[ptr]))
; ;
if (char == ",") { if (char == ",") {
@ -109,4 +102,6 @@
}; };
}; };
}; };
main();

View File

@ -7,7 +7,6 @@ type
OpCode* = enum OpCode* = enum
opReturn, opCall, opCheckArity, opFunctionDef, # functions opReturn, opCall, opCheckArity, opFunctionDef, # functions
opPop, opPopSA, opPopA # pop opPop, opPopSA, opPopA # pop
opPrint, # print TODO move to native func
opNegate, opNot # unary opNegate, opNot # unary
opAdd, opSubtract, opMultiply, opDivide, # math opAdd, opSubtract, opMultiply, opDivide, # math
opEqual, opGreater, opLess, # comparison opEqual, opGreater, opLess, # comparison
@ -18,7 +17,7 @@ type
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps
opCreateList, opCreateTable, # collection creation opCreateList, opCreateTable, # collection creation
opLen, opSetIndex, opGetIndex, # collection operators opLen, opSetIndex, opGetIndex, # collection operators
opChr, opInt, # temporary opcodes for the brainfuck interpreter TODO move to native funcs opPrint, opChr, opInt, opPutchar, # temporary opcodes for the brainfuck interpreter TODO move to native funcs
Chunk* = object Chunk* = object
code*: seq[uint8] code*: seq[uint8]
@ -83,7 +82,7 @@ const simpleInstructions = {
opEqual, opGreater, opLess, opEqual, opGreater, opLess,
opTrue, opFalse, opNil, opTrue, opFalse, opNil,
opLen, opSetIndex, opGetIndex, opLen, opSetIndex, opGetIndex,
opChr, opInt, opChr, opInt, opPutchar,
} }
const constantInstructions = { const constantInstructions = {
opConstant, opConstant,

View File

@ -473,12 +473,15 @@ proc unary(comp: Compiler) =
comp.writeChunk(0, opInt) comp.writeChunk(0, opInt)
of tkChr: of tkChr:
comp.writeChunk(0, opChr) comp.writeChunk(0, opChr)
of tkPutch:
comp.writeChunk(0, opPutchar)
else: else:
discard # unreachable discard # unreachable
tkBang.genRule(unary, nop, pcNone) tkBang.genRule(unary, nop, pcNone)
tkInt.genRule(unary, nop, pcNone) tkInt.genRule(unary, nop, pcNone)
tkChr.genRule(unary, nop, pcNone) tkChr.genRule(unary, nop, pcNone)
tkPutch.genRule(unary, nop, pcNone)
proc binary(comp: Compiler) = proc binary(comp: Compiler) =
let opType = comp.previous.tokenType let opType = comp.previous.tokenType

View File

@ -19,7 +19,7 @@ type
tkIdentifier, tkString, tkIdentifier, tkString,
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil, tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile, tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
tkChr, tkInt, tkChr, tkInt, tkPutch,
tkError, tkEof tkError, tkEof
Token* = object Token* = object
@ -128,6 +128,7 @@ const keywords = {
"while": tkWhile, "while": tkWhile,
"int": tkInt, "int": tkInt,
"chr": tkChr, "chr": tkChr,
"write": tkPutch,
}.toTable }.toTable
proc canStartIdent(chr: char): bool = proc canStartIdent(chr: char): bool =

View File

@ -123,7 +123,7 @@ proc tableFindString*[U, V](tbl: Table[U, V], chars: ptr char, len: int, hash: i
if entry.entryStatus == esNil: if entry.entryStatus == esNil:
return nil return nil
elif entry.key.len.int == len and entry.key.hash.int == hash and elif entry.key.len.int == len and entry.key.hash.int == hash and
equalMem(chars, entry.key.chars[0].unsafeAddr, len): (len == 0 or equalMem(chars, entry.key.chars[0].unsafeAddr, len)):
return entry.key return entry.key
index = (index + 1).bitand(tbl.cap - 1) index = (index + 1).bitand(tbl.cap - 1)

View File

@ -11,20 +11,26 @@ proc free*(ndStr: var NdString) =
# hashes # hashes
proc fnv1a*(ndStr: NdString): int = proc fnv1a*(ndStr: NdString): int {.inline.} =
var hash = 2166136261'u32 var hash = 2166136261'u32
for i in countup(0, ndStr.len.int - 1): for i in countup(0, ndStr.len.int - 1):
hash = hash xor (ndStr.chars[i]).uint32 hash = hash xor (ndStr.chars[i]).uint32
hash *= 16777619 hash *= 16777619
return hash.int return hash.int
proc fnv1a*(str: string): int = proc fnv1a*(str: string): int {.inline.} = # SHOULD RETURN THE SAME HASH AS AN NDSTRING WITH THE SAME CONTENTS
var hash = 2166136261'u32 var hash = 2166136261'u32
for i in countup(0, str.len - 1): for i in countup(0, str.len - 1):
hash = hash xor (str[i]).uint32 hash = hash xor (str[i]).uint32
hash *= 16777619 hash *= 16777619
return hash.int return hash.int
proc fnv1a*(str: char): int {.inline.} = # SHOULD RETURN THE SAME AS A 1 LENGTH STRING
var hash = 2166136261'u32
hash = hash xor str.uint32
hash *= 16777619
return hash.int
# equals # equals
@ -37,25 +43,48 @@ proc newString*(str: string): NdString =
let strlen = str.len() let strlen = str.len()
let hash = str.fnv1a() let hash = str.fnv1a()
let interned = ndStrings.tableFindString(str[0].unsafeAddr, strlen, hash) if strlen > 0:
if interned != nil: let interned = ndStrings.tableFindString(str[0].unsafeAddr, strlen, hash)
return interned if interned != nil:
return interned
else:
let interned = ndStrings.tableFindString(nil, 0, hash)
if interned != nil:
return interned
let len = 8 + strlen let len = 8 + strlen
result = cast[NdString](alloc(len)) result = cast[NdString](alloc(len))
result.len = strlen.uint32 result.len = strlen.uint32
result.hash = hash.uint32 result.hash = hash.uint32
copyMem(result.chars[0].unsafeAddr, str[0].unsafeAddr, strlen) if strlen > 0:
copyMem(result.chars[0].unsafeAddr, str[0].unsafeAddr, strlen)
discard ndStrings.tableSet(result, nil) discard ndStrings.tableSet(result, nil)
proc newString*(str: char): NdString =
let hash = str.fnv1a()
let interned = ndStrings.tableFindString(str.unsafeAddr, 1, hash)
if interned != nil:
return interned
let len = 8 + 1
result = cast[NdString](alloc(len))
result.len = 1.uint32
result.hash = hash.uint32
result.chars[0] = str
discard ndStrings.tableSet(result, nil)
proc resetInternedStrings* = proc resetInternedStrings* =
ndStrings.free() ndStrings.free()
ndStrings = newTable[NdString, NdString]() ndStrings = newTable[NdString, NdString]()
proc `$`*(ndStr: NdString): string = proc `$`*(ndStr: NdString): string =
result = newString(ndStr.len.int) result = newString(ndStr.len.int)
copyMem(result[0].unsafeAddr, ndStr.chars[0].unsafeAddr, ndStr.len.int) if ndStr.len.int > 0:
copyMem(result[0].unsafeAddr, ndStr.chars[0].unsafeAddr, ndStr.len.int)
proc `&`*(left, right: NdString): NdString = proc `&`*(left, right: NdString): NdString =
# TODO optimize this later when strings will be benchmarked # TODO optimize this later when strings will be benchmarked
@ -65,5 +94,7 @@ proc getLength*(ndStr: NdString): int =
ndStr.len.int ndStr.len.int
proc getIndex*(ndStr: NdString, index: int): NdString = proc getIndex*(ndStr: NdString, index: int): NdString =
# TODO optimize this later newString(ndStr.chars[index])
newString($($ndStr)[index])
proc getIndexAsChar*(ndStr: NdString, index: int): char =
ndStr.chars[index]

View File

@ -65,6 +65,12 @@ proc peek*[T](stack: Stack[T]): var T {.inline.} =
raise newException(Defect, "Stacktop is nil or smaller than start.") raise newException(Defect, "Stacktop is nil or smaller than start.")
stack.top[] stack.top[]
proc settip*[T](stack: var Stack[T], newtip: T) {.inline.} =
when boundsChecks:
if stack.top == nil or stack.top.pless(stack.start):
raise newException(Defect, "Stacktop is nil or smaller than start")
stack.top[]= newtip
proc deleteTopN*[T](stack: var Stack[T], n: Natural) = proc deleteTopN*[T](stack: var Stack[T], n: Natural) =
stack.top = stack.top.psub(sizeof(T) * n) stack.top = stack.top.psub(sizeof(T) * n)
when boundsChecks: when boundsChecks:

View File

@ -157,7 +157,7 @@ proc run*(chunk: Chunk): InterpretResult =
runtimeError(res.msg) runtimeError(res.msg)
break break
of opPrint: of opPrint:
write stdout, $stack.peek() echo $stack.peek()
of opDefineGlobal: of opDefineGlobal:
let name = readConstant().asString() let name = readConstant().asString()
let existed = globals.tableSet(name.fromNdString(), stack.pop()) let existed = globals.tableSet(name.fromNdString(), stack.pop())
@ -253,7 +253,7 @@ proc run*(chunk: Chunk): InterpretResult =
break break
of opGetIndex: of opGetIndex:
let index = stack.pop() let index = stack.pop()
let res = stack.peek().getIndex(index) let res = stack.peek().getIndex(index) # getIndex modifies the top value of the stack
if not res.ok: if not res.ok:
runtimeError(res.msg) runtimeError(res.msg)
break break
@ -265,19 +265,31 @@ proc run*(chunk: Chunk): InterpretResult =
runtimeError(res.msg) runtimeError(res.msg)
break break
of opChr: of opChr:
let val = stack.pop() let val = stack.peek()
if not val.isFloat() or val.asFloat() > 255f or val.asFloat() < 0f: if not val.isFloat():
runtimeError("chr on not float or float out of range") runtimeError("chr on not float")
break break
let chr = val.asFloat().int().char() let floatval = val.asFloat()
stack.push(($chr).fromNimString()) if floatval > 255f or floatval < 0f:
runtimeError("chr on a float out of range")
break
let chr = floatval.char()
stack.settip((chr).newString().fromNdString())
of opInt: of opInt:
let val = stack.pop() let val = stack.peek()
if not val.isString(): if not val.isString():
runtimeError("int on non string") runtimeError("int on non string")
break break
let code = ($val.asString())[0].int().float() let strval = val.asString()
stack.push(code.fromFloat()) if strval.getLength() == 0:
runtimeError("int on empty string")
break
let code = val.asString().getIndexAsChar(0).float()
stack.settip(code.fromFloat())
of opPutchar:
write stdout, $stack.peek()
when profileInstructions: when profileInstructions:
durations[ins] += getMonoTime() - startTime durations[ins] += getMonoTime() - startTime

18
tests/ampersand.nds Normal file
View File

@ -0,0 +1,18 @@
// intended use cases of ampersand:
var x = @[1, 2, 3, 4, 5];
x[0] = 0 & [2] = 0;
x[5] = 1 & [6] = 0 & [7] = 3 & [7] = 2;
//expect:@[ 0.0, 2.0, 0.0, 4.0, 5.0, 1.0, 0.0, 2.0 ]
print x;
// not very useful but still must be correct behavior tests:
// change of precedence where interjected
var y = 5 + 1 & * 3;
//expect:18.0
print y;

View File

@ -1,5 +1,8 @@
print "expect: inner douter"; // tests for nested scopes, labels, breaking labels and setting the result of block expressions
//expect:inner
//expect:outer
{ @outer { @outer
{ @middle { @middle
{ @inner { @inner
@ -11,7 +14,9 @@ print "expect: inner douter";
print "outer"; print "outer";
}; };
print "expect: inner middle outer"; //expect:inner
//expect:middle
//expect:outer
{ @outer { @outer
{ @middle { @middle
{ @inner { @inner
@ -23,7 +28,7 @@ print "expect: inner middle outer";
print "outer"; print "outer";
}; };
print "expect: nothing"; // nothing to expect here
{ @outer { @outer
{ @middle { @middle
{ @inner { @inner
@ -37,7 +42,7 @@ print "expect: nothing";
print "outer"; print "outer";
}; };
print "expect 5"; //expect:5.0
var f = funct() { var f = funct() {
var y = 1; var y = 1;
@ -52,7 +57,7 @@ var f = funct() {
print f(); print f();
print "expect 15"; //expect:15.0
f = funct(m, n) f = funct(m, n)
:result = m + n :result = m + n
@ -61,7 +66,7 @@ f = funct(m, n)
print f(f(5, 5), 5); print f(f(5, 5), 5);
print "expect: 10"; //expect:10.0
var g = funct() var g = funct()
:result = {@a :result = {@a
@ -75,7 +80,7 @@ var g = funct()
print g(); print g();
print "expect: 9"; //expect:9.0
var h = funct() var h = funct()
:result = {@a :result = {@a

134
tests/controlflow.nds Normal file
View File

@ -0,0 +1,134 @@
// tests for basic control flow constructs
// if, else
//expect:true
if (true) {
print "true";
};
if (false) {
print "false";
};
// return the condition if falsy
//expect:nil
print if (nil) 5;
//expect:false
print if (false) 5;
// return body if truthy
//expect:5.0
print if (true) 5;
// return else body if falsey and present
//expect:6.0
print if (false) 5 else 6;
// but still only return the if body if truthy
//expect:4.0
print if (true) 4 else 6;
// elseif chains
//expect:4.0
print if (false) 1 else if (false) 2 else if (false) 3 else if (true) 4 else if (false) 5 else 8;
// falsiness, truthiness
// false and nil are falsey
var uninitialized;
if (false) print "don't see this";
if (nil) print "don't see this";
if (uninitialized) print "don't see this";
// the rest of the types are truthy
if (true) print "1";
if ("") print "2";
if ("hello") print "3";
if (0) print "4";
if (1) print "5";
if (@[]) print "6";
if (@["hi"]) print "7";
if (@{}) print "8";
if (@{"hi" = 5}) print "9";
if (funct(n) print n) print "10";
//expect:1
//expect:2
//expect:3
//expect:4
//expect:5
//expect:6
//expect:7
//expect:8
//expect:9
//expect:10
// and, or
// and returns the left one if it's falsey or the right one if the left one is truthy
//expect:false
print false and 5;
//expect:5.0
print true and 5;
// or returns the leftmost truthy
//expect:5.0
print false or false or false or false or 5 or false or 7;
//expect:true
print true or false;
// while
// basic looping examples
var i = 1;
while (i < 300) {
i = i + 1;
};
print i; //expect:300.0
i = 5;
while (i > 0)
print i = i - 1
;
//expect:4.0
//expect:3.0
//expect:2.0
//expect:1.0
//expect:0.0
// while returns the result of the last iteration
i = 5;
var res = while (i > 0)
i = i - 1
;
//expect:0.0
print res;
// if no iterations are done, it returns nil
res = while (false)
i = 6
;
//expect:nil
print res;

View File

@ -1,7 +1,49 @@
import hashtable import hashtable
import ndlist
import os
import re
import strutils
import osproc
testHashtables() testHashtables()
import ndlist testNdlist()
var ndsPath = "bin/nds"
var testsPath = "tests"
proc assembleTestOutput(path: string): string =
for line in path.lines:
if line.contains(re"//expect:"):
result &= line[line.find(re"//expect:") + "//expect:".len()..^1] & "\n"
proc runTest(path: string) =
let (output, exitcode) = execCmdEx(ndsPath & " " & path)
let expoutput = assembleTestOutput(path)
let success = output == expoutput
if not success:
echo "Nds test failed: " & path
echo "expected output:"
echo expoutput
echo "got output:"
echo output
else:
echo "Test success: " & path
if not dirExists(testsPath):
testsPath = "."
if not fileExists(ndsPath):
if fileExists(".." / ndsPath):
ndsPath = ".." / ndsPath
else:
echo "Couldn't find nds binary in bin/nds or ../bin/nds, run nimble build first."
quit 1
for test in walkDir(testsPath):
if test.path.splitFile.ext == ".nds":
runTest(test.path)
testNdlist()

View File

@ -1,8 +0,0 @@
var i = 1;
while (i < 300) {
i = i + 1;
};
print i;

View File

@ -1,4 +0,0 @@
var i = 5;
while (i > 0)
print i = i - 1
;