diff --git a/benchmarks/fib.nds b/benchmarks/fib.nds index c48a94c..573dcc5 100644 --- a/benchmarks/fib.nds +++ b/benchmarks/fib.nds @@ -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); \ No newline at end of file +print fib(37); \ No newline at end of file diff --git a/examples/bf.nds b/examples/bf.nds index 30b5680..ddca6f8 100644 --- a/examples/bf.nds +++ b/examples/bf.nds @@ -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 @@ }; }; -}; \ No newline at end of file +}; + +main(); \ No newline at end of file diff --git a/src/ndspkg/chunk.nim b/src/ndspkg/chunk.nim index 2307c28..be6d149 100644 --- a/src/ndspkg/chunk.nim +++ b/src/ndspkg/chunk.nim @@ -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, diff --git a/src/ndspkg/compiler.nim b/src/ndspkg/compiler.nim index 98c24ec..ae21c25 100644 --- a/src/ndspkg/compiler.nim +++ b/src/ndspkg/compiler.nim @@ -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 diff --git a/src/ndspkg/scanner.nim b/src/ndspkg/scanner.nim index 9113451..b1754c7 100644 --- a/src/ndspkg/scanner.nim +++ b/src/ndspkg/scanner.nim @@ -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 = diff --git a/src/ndspkg/types/hashtable.nim b/src/ndspkg/types/hashtable.nim index 518f08c..4008467 100644 --- a/src/ndspkg/types/hashtable.nim +++ b/src/ndspkg/types/hashtable.nim @@ -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) diff --git a/src/ndspkg/types/ndstring.nim b/src/ndspkg/types/ndstring.nim index 320bf98..270aaef 100644 --- a/src/ndspkg/types/ndstring.nim +++ b/src/ndspkg/types/ndstring.nim @@ -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]) \ No newline at end of file + newString(ndStr.chars[index]) + +proc getIndexAsChar*(ndStr: NdString, index: int): char = + ndStr.chars[index] \ No newline at end of file diff --git a/src/ndspkg/types/stack.nim b/src/ndspkg/types/stack.nim index 760cfcc..c797cb6 100644 --- a/src/ndspkg/types/stack.nim +++ b/src/ndspkg/types/stack.nim @@ -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: diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index 4450aa2..7d1e41a 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -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 diff --git a/tests/ampersand.nds b/tests/ampersand.nds new file mode 100644 index 0000000..3d7d2d7 --- /dev/null +++ b/tests/ampersand.nds @@ -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; + diff --git a/tests/break.nds b/tests/break.nds index 23e3e88..cbb37de 100644 --- a/tests/break.nds +++ b/tests/break.nds @@ -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 diff --git a/tests/controlflow.nds b/tests/controlflow.nds new file mode 100644 index 0000000..a6ae279 --- /dev/null +++ b/tests/controlflow.nds @@ -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; diff --git a/tests/test.nim b/tests/test.nim index e2ec4d6..1d397b9 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -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() \ No newline at end of file diff --git a/tests/while.nds b/tests/while.nds deleted file mode 100644 index ed1503d..0000000 --- a/tests/while.nds +++ /dev/null @@ -1,8 +0,0 @@ - -var i = 1; - -while (i < 300) { - i = i + 1; -}; - -print i; \ No newline at end of file diff --git a/tests/while2.nds b/tests/while2.nds deleted file mode 100644 index 36d1beb..0000000 --- a/tests/while2.nds +++ /dev/null @@ -1,4 +0,0 @@ -var i = 5; -while (i > 0) - print i = i - 1 -; \ No newline at end of file