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)
if (n < 2) :result = 1.5
if (n < 2) :result = 1
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
var src = ">++++++++++>+>+[
[+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[
[-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-
[>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>>
]<<<
]";
var src = ">++++++++++[<++++++++++>-]<->>>>>+++[>+++>+++<<-]<<<<+<[>[>+>+<<-]>>[-<<+>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]>>>>>>[[-]<++++++++++<->>]<-[>+>+<<-]>[<+>-]+>[[-]<->]<<<<<<<<<->>]<[>+>+<<-]>>[-<<+>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+>+<<<-]>>>[-<<<+>>>]++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<<[>>+>+<<<-]>>>[-<<<+>>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<[[-]>>>++++++++[>>++++++<<-]>[<++++++++[>++++++<-]>.<++++++++[>------<-]>[<<+>>-]]>.<<++++++++[>>------<<-]<[->>+<<]<++++++++[<++++>-]<.>+++++++[>+++++++++<-]>+++.<+++++[>+++++++++<-]>.+++++..--------.-------.++++++++++++++>>[>>>+>+<<<<-]>>>>[-<<<<+>>>>]>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<<[>>>+>+<<<<-]>>>>[-<<<<+>>>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+<<[-]]>[>+<[-]]++>>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<+<[[-]>-<]>[<<<<<<<.>>>>>>>[-]]<<<<<<<<<.>>----.---------.<<.>>----.+++..+++++++++++++.[-]<<[-]]<[>+>+<<-]>>[-<<+>>]+>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<<[>>+>+<<<-]>>>[-<<<+>>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[>+<[-]]<[>>+<<[-]]>>[<<+>>[-]]<<[[-]>++++++++[<++++>-]<.>++++++++++[>+++++++++++<-]>+.-.<<.>>++++++.------------.---.<<.>++++++[>+++<-]>.<++++++[>----<-]>++.+++++++++++..[-]<<[-]++++++++++.[-]]<[>+>+<<-]>>[-<<+>>]+++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]++++++++++. >+++++++++[>+++++++++<-]>+++.+++++++++++++.++++++++++.------.<++++++++[>>++++<<-]>>.<++++++++++.-.---------.>.<-.+++++++++++.++++++++.---------.>.<-------------.+++++++++++++.----------.>.<++++++++++++.---------------.<+++[>++++++<-]>..>.<----------.+++++++++++.>.<<+++[>------<-]>-.+++++++++++++++++.---.++++++.-------.----------.[-]>[-]<<<.[-]]<[>+>+<<-]>>[-<<+>>]++++>+<[-<->]<[[-]>>-<<]>>[[-]<<+>>]<<[[-]++++++++++.[-]<[-]>]<+<]";
var pos = 0;
var len = #src;
var char;
@ -20,8 +15,6 @@
var ptr = 0;
var ptrmax = 30000 - 1;
var i = 0;
while (i < ptrmax) {
tape[i] = 0;
@ -61,7 +54,7 @@
;
if (char == ".")
print(chr(tape[ptr]))
write(chr(tape[ptr]))
;
if (char == ",") {
@ -109,4 +102,6 @@
};
};
};
};
main();

View File

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

View File

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

View File

@ -19,7 +19,7 @@ type
tkIdentifier, tkString,
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
tkChr, tkInt,
tkChr, tkInt, tkPutch,
tkError, tkEof
Token* = object
@ -128,6 +128,7 @@ const keywords = {
"while": tkWhile,
"int": tkInt,
"chr": tkChr,
"write": tkPutch,
}.toTable
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:
return nil
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
index = (index + 1).bitand(tbl.cap - 1)

View File

@ -11,20 +11,26 @@ proc free*(ndStr: var NdString) =
# hashes
proc fnv1a*(ndStr: NdString): int =
proc fnv1a*(ndStr: NdString): int {.inline.} =
var hash = 2166136261'u32
for i in countup(0, ndStr.len.int - 1):
hash = hash xor (ndStr.chars[i]).uint32
hash *= 16777619
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
for i in countup(0, str.len - 1):
hash = hash xor (str[i]).uint32
hash *= 16777619
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
@ -37,25 +43,48 @@ proc newString*(str: string): NdString =
let strlen = str.len()
let hash = str.fnv1a()
let interned = ndStrings.tableFindString(str[0].unsafeAddr, strlen, hash)
if interned != nil:
return interned
if strlen > 0:
let interned = ndStrings.tableFindString(str[0].unsafeAddr, strlen, hash)
if interned != nil:
return interned
else:
let interned = ndStrings.tableFindString(nil, 0, hash)
if interned != nil:
return interned
let len = 8 + strlen
result = cast[NdString](alloc(len))
result.len = strlen.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)
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* =
ndStrings.free()
ndStrings = newTable[NdString, NdString]()
proc `$`*(ndStr: NdString): string =
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 =
# TODO optimize this later when strings will be benchmarked
@ -65,5 +94,7 @@ proc getLength*(ndStr: NdString): int =
ndStr.len.int
proc getIndex*(ndStr: NdString, index: int): NdString =
# TODO optimize this later
newString($($ndStr)[index])
newString(ndStr.chars[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.")
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) =
stack.top = stack.top.psub(sizeof(T) * n)
when boundsChecks:

View File

@ -157,7 +157,7 @@ proc run*(chunk: Chunk): InterpretResult =
runtimeError(res.msg)
break
of opPrint:
write stdout, $stack.peek()
echo $stack.peek()
of opDefineGlobal:
let name = readConstant().asString()
let existed = globals.tableSet(name.fromNdString(), stack.pop())
@ -253,7 +253,7 @@ proc run*(chunk: Chunk): InterpretResult =
break
of opGetIndex:
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:
runtimeError(res.msg)
break
@ -265,19 +265,31 @@ proc run*(chunk: Chunk): InterpretResult =
runtimeError(res.msg)
break
of opChr:
let val = stack.pop()
if not val.isFloat() or val.asFloat() > 255f or val.asFloat() < 0f:
runtimeError("chr on not float or float out of range")
let val = stack.peek()
if not val.isFloat():
runtimeError("chr on not float")
break
let chr = val.asFloat().int().char()
stack.push(($chr).fromNimString())
let floatval = val.asFloat()
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:
let val = stack.pop()
let val = stack.peek()
if not val.isString():
runtimeError("int on non string")
break
let code = ($val.asString())[0].int().float()
stack.push(code.fromFloat())
let strval = val.asString()
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:
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
{ @middle
{ @inner
@ -11,7 +14,9 @@ print "expect: inner douter";
print "outer";
};
print "expect: inner middle outer";
//expect:inner
//expect:middle
//expect:outer
{ @outer
{ @middle
{ @inner
@ -23,7 +28,7 @@ print "expect: inner middle outer";
print "outer";
};
print "expect: nothing";
// nothing to expect here
{ @outer
{ @middle
{ @inner
@ -37,7 +42,7 @@ print "expect: nothing";
print "outer";
};
print "expect 5";
//expect:5.0
var f = funct() {
var y = 1;
@ -52,7 +57,7 @@ var f = funct() {
print f();
print "expect 15";
//expect:15.0
f = funct(m, n)
:result = m + n
@ -61,7 +66,7 @@ f = funct(m, n)
print f(f(5, 5), 5);
print "expect: 10";
//expect:10.0
var g = funct()
:result = {@a
@ -75,7 +80,7 @@ var g = funct()
print g();
print "expect: 9";
//expect:9.0
var h = funct()
: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 ndlist
import os
import re
import strutils
import osproc
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
;