mirror of https://github.com/japl-lang/japl.git
Merge pull request #37 from Productive2/master
TUI improvements for JATS
This commit is contained in:
commit
476a6b6c6b
13
src/japl.nim
13
src/japl.nim
|
@ -31,7 +31,8 @@ proc repl(bytecodeVM: VM) =
|
|||
if bytecodeVM == nil:
|
||||
bytecodeVM = initVM()
|
||||
echo JAPL_VERSION_STRING
|
||||
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
||||
let nimDetails = &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
||||
echo nimDetails
|
||||
var source = ""
|
||||
while true:
|
||||
try:
|
||||
|
@ -46,10 +47,14 @@ proc repl(bytecodeVM: VM) =
|
|||
bytecodeVM.freeVM()
|
||||
break
|
||||
if source == "//clear" or source == "// clear":
|
||||
echo "\x1Bc"
|
||||
echo JAPL_VERSION_STRING
|
||||
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
||||
echo "\x1Bc" & JAPL_VERSION_STRING
|
||||
echo nimDetails
|
||||
continue
|
||||
elif source == "//exit" or source == "// exit":
|
||||
echo "Goodbye!"
|
||||
echo JAPL_VERSION_STRING
|
||||
echo nimDetails
|
||||
break
|
||||
elif source != "":
|
||||
discard bytecodeVM.interpret(source, "stdin")
|
||||
if not bytecodeVM.lastPop.isNil():
|
||||
|
|
|
@ -47,6 +47,12 @@ proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
|||
# to nil
|
||||
return (kind: retNative.Nil, result: nil)
|
||||
|
||||
proc natReadline*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||
## Native function readline
|
||||
## Reads a line from stdin and returns
|
||||
## it as a string.
|
||||
return (kind: retNative.Object, result: stdin.readLine().asStr())
|
||||
|
||||
|
||||
proc natClock*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||
## Native function clock
|
||||
|
|
|
@ -718,6 +718,7 @@ proc initStdlib*(vm: VM) =
|
|||
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
|
||||
vm.defineGlobal("toString", newNative("toString", natToString, 1))
|
||||
vm.defineGlobal("type", newNative("type", natType, 1))
|
||||
vm.defineGlobal("readline", newNative("readline", natReadline, 0))
|
||||
when DEBUG_TRACE_VM and SKIP_STDLIB_INIT:
|
||||
echo "DEBUG - VM: Skipping stdlib initialization"
|
||||
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
//int arithmetic
|
||||
|
||||
print(7+5); //output:12
|
||||
print(-8); //output:-8
|
||||
print(5-8); //output:-3
|
||||
print(1+1+1+1+1); //output:5
|
||||
print(1-1+1-1+1-1); //output:0
|
||||
print(2*3+2);//output:8
|
||||
print(2+3*2);//output:8
|
||||
print(3+2*7);//output:17
|
||||
print(2-9*5);//output:-43
|
||||
print(2*9-5);//output:13
|
||||
print(2**5);//output:32
|
||||
print(3**3);//output:27
|
||||
print(3**3*2);//output:54
|
||||
print(8+2**4);//output:24
|
||||
print(2+7*2+4);//output:20
|
||||
print(1-2**2*5);//output:-19
|
||||
print(7*-2**3+4*7);//output:-28
|
||||
print(-2**2);//output:-4
|
||||
print((-2)**2);//output:4
|
||||
print(-2**3);//output:-8
|
||||
print((-2)**3);//output:-8
|
||||
print((2+3)*4);//output:20
|
||||
print(2*(2+2)*2);//output:16
|
||||
print(2*(2*2)*2);//output:16
|
||||
print(8%5);//output:3
|
||||
print(4%3);//output:1
|
||||
print(8/4);//output:2.0
|
||||
print(28/7/4);//output:1.0
|
||||
print(64/-64);//output:-1.0
|
||||
print(8/0);//output:inf
|
||||
print(8/-0);//output:inf
|
||||
print(-8/0);//output:-inf
|
||||
print(7+5); //stdout:12
|
||||
print(-8); //stdout:-8
|
||||
print(5-8); //stdout:-3
|
||||
print(1+1+1+1+1); //stdout:5
|
||||
print(1-1+1-1+1-1); //stdout:0
|
||||
print(2*3+2);//stdout:8
|
||||
print(2+3*2);//stdout:8
|
||||
print(3+2*7);//stdout:17
|
||||
print(2-9*5);//stdout:-43
|
||||
print(2*9-5);//stdout:13
|
||||
print(2**5);//stdout:32
|
||||
print(3**3);//stdout:27
|
||||
print(3**3*2);//stdout:54
|
||||
print(8+2**4);//stdout:24
|
||||
print(2+7*2+4);//stdout:20
|
||||
print(1-2**2*5);//stdout:-19
|
||||
print(7*-2**3+4*7);//stdout:-28
|
||||
print(-2**2);//stdout:-4
|
||||
print((-2)**2);//stdout:4
|
||||
print(-2**3);//stdout:-8
|
||||
print((-2)**3);//stdout:-8
|
||||
print((2+3)*4);//stdout:20
|
||||
print(2*(2+2)*2);//stdout:16
|
||||
print(2*(2*2)*2);//stdout:16
|
||||
print(8%5);//stdout:3
|
||||
print(4%3);//stdout:1
|
||||
print(8/4);//stdout:2.0
|
||||
print(28/7/4);//stdout:1.0
|
||||
print(64/-64);//stdout:-1.0
|
||||
print(8/0);//stdout:inf
|
||||
print(8/-0);//stdout:inf
|
||||
print(-8/0);//stdout:-inf
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
print(~5 | 5);//output:-1
|
||||
print(1 | 2);//output:3
|
||||
print(1 & 2);//output:0
|
||||
print(~124 & 124);//output:0
|
||||
print(1 | 2 | 4 | 8);//output:15
|
||||
print(32 | 64);//output:96
|
||||
print(96 & 32);//output:32
|
||||
print(~0);//output:-1
|
||||
print(~356);//output:-357
|
||||
print(~5 | 5);//stdout:-1
|
||||
print(1 | 2);//stdout:3
|
||||
print(1 & 2);//stdout:0
|
||||
print(~124 & 124);//stdout:0
|
||||
print(1 | 2 | 4 | 8);//stdout:15
|
||||
print(32 | 64);//stdout:96
|
||||
print(96 & 32);//stdout:32
|
||||
print(~0);//stdout:-1
|
||||
print(~356);//stdout:-357
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
print(2 or 3);//output:2
|
||||
print(2 and 3);//output:3
|
||||
print(false or true);//output:true
|
||||
print(true or false);//output:true
|
||||
print(true and false);//output:false
|
||||
print(false and 3 or 4);//output:4
|
||||
print(true and 3 or 4);//output:3
|
||||
print(true and 2);//output:2
|
||||
print(false or 5);//output:5
|
||||
print(0 or 4);//output:4
|
||||
print(0 and true);//output:0
|
||||
print("" and true);//output:''
|
||||
print("" or true);//output:true
|
||||
print(1 or 2 or 3 or 4);//output:1
|
||||
print(1 and 2 and 3 and 4);//output:4
|
||||
print(1 and 2 or 3 and 4);//output:2
|
||||
print(1 and false or 3 and 4);//output:4
|
||||
print(not 0);//output:true
|
||||
print(not 1);//output:false
|
||||
print(not 1 and not 2);//output:false
|
||||
print(not (1 and 0));//output:true
|
||||
print(2 or 3);//stdout:2
|
||||
print(2 and 3);//stdout:3
|
||||
print(false or true);//stdout:true
|
||||
print(true or false);//stdout:true
|
||||
print(true and false);//stdout:false
|
||||
print(false and 3 or 4);//stdout:4
|
||||
print(true and 3 or 4);//stdout:3
|
||||
print(true and 2);//stdout:2
|
||||
print(false or 5);//stdout:5
|
||||
print(0 or 4);//stdout:4
|
||||
print(0 and true);//stdout:0
|
||||
print("" and true);//stdout:''
|
||||
print("" or true);//stdout:true
|
||||
print(1 or 2 or 3 or 4);//stdout:1
|
||||
print(1 and 2 and 3 and 4);//stdout:4
|
||||
print(1 and 2 or 3 and 4);//stdout:2
|
||||
print(1 and false or 3 and 4);//stdout:4
|
||||
print(not 0);//stdout:true
|
||||
print(not 1);//stdout:false
|
||||
print(not 1 and not 2);//stdout:false
|
||||
print(not (1 and 0));//stdout:true
|
||||
|
|
|
@ -15,4 +15,4 @@ fun mul2(x)
|
|||
print(add2(sub2(mul2(sub2(5)))));
|
||||
//5-2=3
|
||||
//3*2=6
|
||||
//output:6
|
||||
//stdout:6
|
||||
|
|
|
@ -2,41 +2,41 @@ var x = 4;
|
|||
var y = 5;
|
||||
var z = 6;
|
||||
if (x < y)
|
||||
print("1");//output:1
|
||||
print("1");//stdout:1
|
||||
else
|
||||
print("2");
|
||||
|
||||
if (x == y)
|
||||
print("3");//output:4
|
||||
print("3");//stdout:4
|
||||
else
|
||||
print("4");
|
||||
|
||||
if (x > y)
|
||||
print("5");//output:6
|
||||
print("5");//stdout:6
|
||||
else if (x < y)
|
||||
print("6");
|
||||
|
||||
if (y >= 5)
|
||||
print("7");//output:7
|
||||
print("7");//stdout:7
|
||||
else
|
||||
print("8");
|
||||
|
||||
if (z >= 5)
|
||||
print("9");//output:9
|
||||
print("9");//stdout:9
|
||||
else
|
||||
print("10");
|
||||
|
||||
if (x <= 4)
|
||||
print("11");//output:11
|
||||
print("11");//stdout:11
|
||||
else
|
||||
print("12");
|
||||
|
||||
if (2 <= y)
|
||||
print("13");//output:13
|
||||
print("13");//stdout:13
|
||||
else
|
||||
print("14");
|
||||
|
||||
if (8 <= z)
|
||||
print("15");
|
||||
else
|
||||
print("16");//output:16
|
||||
print("16");//stdout:16
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
var a = "hello";
|
||||
var b = "hello";
|
||||
print(a is b);//output:true
|
||||
print(a is b);//stdout:true
|
||||
|
||||
//different strings
|
||||
|
||||
var x = "ex";
|
||||
var y = "ey";
|
||||
print(x is y);//output:false
|
||||
print(x is y);//stdout:false
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var a = 1; { var a = a; }
|
||||
//output:A fatal error occurred while compiling '''', line 1, at ';' -> cannot read local variable in its own initializer
|
||||
//stdout:A fatal error occurred while compiling '''', line 1, at ';' -> cannot read local variable in its own initializer
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
var a = b;
|
||||
//output:An unhandled exception occurred, traceback below:
|
||||
//stdout:An unhandled exception occurred, traceback below:
|
||||
|
||||
//output: File '''', line 1, in <module>:
|
||||
//stdout: File '''', line 1, in <module>:
|
||||
|
||||
//output:ReferenceError: undefined name 'b'
|
||||
//stdout:ReferenceError: undefined name 'b'
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
var a = 2 + "hey";
|
||||
//output:An unhandled exception occurred, traceback below:
|
||||
//stdout:An unhandled exception occurred, traceback below:
|
||||
|
||||
//output: File '''', line 1, in <module>:
|
||||
//stdout: File '''', line 1, in <module>:
|
||||
|
||||
//output:TypeError: unsupported binary operator '+' for objects of type 'integer' and 'string'
|
||||
//stdout:TypeError: unsupported binary operator '+' for objects of type 'integer' and 'string'
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -8,4 +8,4 @@ for (var x = 3; x < 1001; x = x + 1)
|
|||
sum = sum + x;
|
||||
}
|
||||
}
|
||||
print(sum);//output:234168
|
||||
print(sum);//stdout:234168
|
||||
|
|
|
@ -13,5 +13,5 @@ while (a < 4000000)
|
|||
a = b;
|
||||
b = c;
|
||||
}
|
||||
print(sum);//output:4613732
|
||||
print(sum);//stdout:4613732
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ fun fib(n) {
|
|||
return n;
|
||||
return fib(n-2) + fib(n-1);
|
||||
}
|
||||
print(fib(1));//output:1
|
||||
print(fib(2));//output:1
|
||||
print(fib(3));//output:2
|
||||
print(fib(4));//output:3
|
||||
print(fib(5));//output:5
|
||||
print(fib(6));//output:8
|
||||
print(fib(7));//output:13
|
||||
print(fib(8));//output:21
|
||||
print(fib(9));//output:34
|
||||
print(fib(1));//stdout:1
|
||||
print(fib(2));//stdout:1
|
||||
print(fib(3));//stdout:2
|
||||
print(fib(4));//stdout:3
|
||||
print(fib(5));//stdout:5
|
||||
print(fib(6));//stdout:8
|
||||
print(fib(7));//stdout:13
|
||||
print(fib(8));//stdout:21
|
||||
print(fib(9));//stdout:34
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
for (var x = 0; x < 2; x = x + 1)
|
||||
{
|
||||
print(x);
|
||||
//output:0
|
||||
//output:1
|
||||
//stdout:0
|
||||
//stdout:1
|
||||
}
|
||||
|
|
|
@ -17,70 +17,70 @@ var i = 0;
|
|||
for (; i != -1; i = next(i))
|
||||
print(i);
|
||||
// before using next
|
||||
//output:0
|
||||
//stdout:0
|
||||
// y = 0
|
||||
//output:1
|
||||
//output:2
|
||||
//output:3
|
||||
//output:4
|
||||
//output:5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:1
|
||||
//stdout:2
|
||||
//stdout:3
|
||||
//stdout:4
|
||||
//stdout:5
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 1
|
||||
//output:2
|
||||
//output:3
|
||||
//output:4
|
||||
//output:5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:2
|
||||
//stdout:3
|
||||
//stdout:4
|
||||
//stdout:5
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 2
|
||||
//output:3
|
||||
//output:4
|
||||
//output:5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:3
|
||||
//stdout:4
|
||||
//stdout:5
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 3
|
||||
//output:4
|
||||
//output:5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:4
|
||||
//stdout:5
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 4
|
||||
//output:5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:5
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 5
|
||||
//output:6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:6
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 6
|
||||
//output:7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:7
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 7
|
||||
//output:8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:8
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 8
|
||||
//output:9
|
||||
//output:10
|
||||
//stdout:9
|
||||
//stdout:10
|
||||
// y = 9
|
||||
//output:10
|
||||
//stdout:10
|
||||
// y = 10
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
print("Hello, world.");
|
||||
//output:Hello, world.
|
||||
//stdout:Hello, world.
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
print("Hello, JAPL.");
|
||||
//output:Hello, JAPL.
|
||||
//stdout:Hello, JAPL.
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var x = 5;
|
||||
if (x > 2)
|
||||
{
|
||||
print("large");//output:large
|
||||
print("large");//stdout:large
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -12,17 +12,17 @@ if (x < 4)
|
|||
{
|
||||
print("large");//won't run
|
||||
}
|
||||
print("beep");//output:beep
|
||||
print("beep");//stdout:beep
|
||||
|
||||
if (x == 5)
|
||||
print("equal to 5");//output:equal to 5
|
||||
print("equal to 5");//stdout:equal to 5
|
||||
|
||||
if (2 != x)
|
||||
print("not 2");//output:not 2
|
||||
print("not 2");//stdout:not 2
|
||||
else
|
||||
print("2");
|
||||
|
||||
if (2 == x)
|
||||
print("2");
|
||||
else
|
||||
print("not 2");//output:not 2
|
||||
print("not 2");//stdout:not 2
|
||||
|
|
|
@ -13,10 +13,10 @@ fun printInt(x) {
|
|||
print("six");
|
||||
}
|
||||
var x = 3;
|
||||
printInt(x);//output:three
|
||||
printInt(x);//stdout:three
|
||||
x = 5;
|
||||
printInt(x);//output:five
|
||||
printInt(x);//stdout:five
|
||||
x = 7;
|
||||
printInt(x);
|
||||
x = 1;
|
||||
printInt(x);//output:one
|
||||
printInt(x);//stdout:one
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
//stdin:Hello world!
|
||||
print(readline());
|
||||
//stdout:Hello world!
|
|
@ -1,22 +1,22 @@
|
|||
var x = 4;
|
||||
var y = x;
|
||||
|
||||
print(x is y);//output:true
|
||||
print(x is x);//output:true
|
||||
print(x is 4);//output:false
|
||||
print(x is y);//stdout:true
|
||||
print(x is x);//stdout:true
|
||||
print(x is 4);//stdout:false
|
||||
|
||||
var z = true;
|
||||
var u = true;
|
||||
|
||||
print(z is u);//output:true
|
||||
print(z is x);//output:false
|
||||
print(z is z);//output:true
|
||||
print(z is u);//stdout:true
|
||||
print(z is x);//stdout:false
|
||||
print(z is z);//stdout:true
|
||||
|
||||
var l = false;
|
||||
print((not l) is z);//output:true
|
||||
print(l is z);//output:false
|
||||
print((l is z) is l);//output:true
|
||||
print((not l) is z);//stdout:true
|
||||
print(l is z);//stdout:false
|
||||
print((l is z) is l);//stdout:true
|
||||
|
||||
var k;
|
||||
print(k is nil);//output:true
|
||||
print(k is nil);//stdout:true
|
||||
|
||||
|
|
|
@ -15,4 +15,4 @@ var mul2 = lambda(x)
|
|||
print(add2(sub2(mul2(sub2(5)))));
|
||||
//5-2=3
|
||||
//3*2=6
|
||||
//output:6
|
||||
//stdout:6
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
print((5/0)*0);
|
||||
|
||||
//output:nan
|
||||
//stdout:nan
|
||||
|
||||
//output:
|
||||
//stdout:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
var x = 5;
|
||||
var y = x;
|
||||
y = 6;
|
||||
print(x);//output:5
|
||||
print(x);//stdout:5
|
||||
}
|
||||
|
||||
var g = 7;
|
||||
|
@ -11,9 +11,9 @@ var p = g;
|
|||
var k = g;
|
||||
p = 3;
|
||||
k = 9;
|
||||
print(g);//output:7
|
||||
print(g);//stdout:7
|
||||
}
|
||||
print(g);//output:7
|
||||
print(g);//stdout:7
|
||||
|
||||
fun resetter(x) {
|
||||
x = 7;
|
||||
|
@ -21,5 +21,5 @@ fun resetter(x) {
|
|||
}
|
||||
|
||||
var q = 5;
|
||||
resetter(q);//output:7
|
||||
print(q);//output:5
|
||||
resetter(q);//stdout:7
|
||||
print(q);//stdout:5
|
||||
|
|
|
@ -4,12 +4,12 @@ var f = "leafy";
|
|||
var g = "ishere";
|
||||
var h = f + g;
|
||||
var j = "leafyishere";
|
||||
print(h is j);//output:true
|
||||
print(h is j);//stdout:true
|
||||
|
||||
//different strings
|
||||
|
||||
var x = "ex";
|
||||
var y = "ey";
|
||||
print(x is y);//output:false
|
||||
print(x is y);//stdout:false
|
||||
|
||||
|
||||
|
|
|
@ -4,73 +4,73 @@
|
|||
var x = 4;
|
||||
{
|
||||
var x = 5;
|
||||
print(x);//output:5
|
||||
print(x);//stdout:5
|
||||
}
|
||||
print(x);//output:4
|
||||
print(x);//stdout:4
|
||||
|
||||
// type changing shadowing
|
||||
var y = true;
|
||||
{
|
||||
var y = 2;
|
||||
print(y);//output:2
|
||||
print(y);//stdout:2
|
||||
}
|
||||
print(y);//output:true
|
||||
print(y);//stdout:true
|
||||
|
||||
// no shadowing here
|
||||
var z = 3;
|
||||
{
|
||||
z = true;
|
||||
print(z);//output:true
|
||||
print(z);//stdout:true
|
||||
}
|
||||
print(z);//output:true
|
||||
print(z);//stdout:true
|
||||
|
||||
//in-function shadowing
|
||||
fun shadow(x) {
|
||||
//will be called once with the input 3
|
||||
print(x);//output:3
|
||||
print(x);//stdout:3
|
||||
{
|
||||
var x = 4;
|
||||
print(x);//output:4
|
||||
print(x);//stdout:4
|
||||
}
|
||||
print(x);//output:3
|
||||
print(x);//stdout:3
|
||||
x = nil;
|
||||
print(x);//output:nil
|
||||
print(x);//stdout:nil
|
||||
return x;
|
||||
}
|
||||
|
||||
print(shadow(3));//output:nil
|
||||
print(shadow(3));//stdout:nil
|
||||
|
||||
//shadowing functions
|
||||
fun hello() {
|
||||
print("hello");
|
||||
}
|
||||
hello();//output:hello
|
||||
hello();//stdout:hello
|
||||
{
|
||||
fun hello() {
|
||||
print("hello in");
|
||||
}
|
||||
hello();//output:hello in
|
||||
hello();//stdout:hello in
|
||||
{
|
||||
fun hello() {
|
||||
print("hello inmost");
|
||||
}
|
||||
hello();//output:hello inmost
|
||||
hello();//stdout:hello inmost
|
||||
}
|
||||
hello();//output:hello in
|
||||
hello();//stdout:hello in
|
||||
}
|
||||
hello();//output:hello
|
||||
hello();//stdout:hello
|
||||
|
||||
//functions shadowing with type change
|
||||
fun eat() {
|
||||
print("nom nom nom");
|
||||
}
|
||||
eat();//output:nom nom nom
|
||||
eat();//stdout:nom nom nom
|
||||
{
|
||||
var eat = 4;
|
||||
print(eat);//output:4
|
||||
print(eat);//stdout:4
|
||||
{{{{{
|
||||
eat = 5;
|
||||
}}}}} //multiple scopes haha
|
||||
print(eat);//output:5
|
||||
print(eat);//stdout:5
|
||||
}
|
||||
eat();//output:nom nom nom
|
||||
eat();//stdout:nom nom nom
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
var left = "left";
|
||||
var right = "right";
|
||||
var directions = left + " " + right;
|
||||
print(directions);//output:left right
|
||||
print(directions);//stdout:left right
|
||||
|
||||
var longstring = directions * 5;
|
||||
print(longstring);//output:left rightleft rightleft rightleft rightleft right
|
||||
print(longstring);//stdout:left rightleft rightleft rightleft rightleft right
|
||||
|
||||
left = left + " side";
|
||||
print(left);//output:left side
|
||||
print(left);//stdout:left side
|
||||
right = "side: " + right;
|
||||
print(right);//output:side: right
|
||||
print(right);//stdout:side: right
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
var x = 1;
|
||||
var y = 2;
|
||||
print(x);//output:1
|
||||
print(y);//output:2
|
||||
print(x);//stdout:1
|
||||
print(y);//stdout:2
|
||||
{
|
||||
var x = 4;
|
||||
var y = 5;
|
||||
print(x);//output:4
|
||||
print(y);//output:5
|
||||
print(x);//stdout:4
|
||||
print(y);//stdout:5
|
||||
{
|
||||
var z = 6;
|
||||
var y = 2;
|
||||
print(x);//output:4
|
||||
print(y);//output:2
|
||||
print(x);//stdout:4
|
||||
print(y);//stdout:2
|
||||
}
|
||||
print(x);//output:4
|
||||
print(y);//output:5
|
||||
print(x);//stdout:4
|
||||
print(y);//stdout:5
|
||||
}
|
||||
|
||||
print(x);//output:1
|
||||
print(y);//output:2
|
||||
print(x);//stdout:1
|
||||
print(y);//stdout:2
|
||||
|
||||
var longName;
|
||||
print(longName); //output:nil
|
||||
print(longName); //stdout:nil
|
||||
longName = 5;
|
||||
print(longName); //output:5
|
||||
print(longName); //stdout:5
|
||||
longName = "hello";
|
||||
print(longName); //output:hello
|
||||
print(longName); //stdout:hello
|
||||
longName = longName + " world";
|
||||
print(longName); //output:hello world
|
||||
print(longName); //stdout:hello world
|
||||
|
|
|
@ -3,11 +3,11 @@ while (x > 0)
|
|||
{
|
||||
x = x - 1;
|
||||
print(x);
|
||||
//output:4
|
||||
//output:3
|
||||
//output:2
|
||||
//output:1
|
||||
//output:0
|
||||
//stdout:4
|
||||
//stdout:3
|
||||
//stdout:2
|
||||
//stdout:1
|
||||
//stdout:0
|
||||
}
|
||||
|
||||
var string = "h";
|
||||
|
@ -17,4 +17,4 @@ while (x < 10)
|
|||
x = x + 1;
|
||||
string = string + "A";
|
||||
}
|
||||
print(string);//output:hAAAAAAAAAA
|
||||
print(string);//stdout:hAAAAAAAAAA
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# Just Another Test Runner for running JAPL tests
|
||||
# a testrunner process
|
||||
|
||||
|
|
301
tests/jats.nim
301
tests/jats.nim
|
@ -1,172 +1,163 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Just Another Test Suite for running JAPL tests
|
||||
|
||||
import nim/nimtests
|
||||
|
||||
import ../src/vm
|
||||
import testutils
|
||||
import testobject, testutils, logutils
|
||||
|
||||
import os, osproc, strformat, streams
|
||||
|
||||
# Tests that represent not-yet implemented behaviour
|
||||
const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl", "problem4.jpl"]
|
||||
# TODO: for_with_function.jpl and problem4.jpl should already be implemented, check on them
|
||||
|
||||
proc buildTest(path: string): Test =
|
||||
log(LogLevel.Debug, &"Building test {path}")
|
||||
let source = readFile(path)
|
||||
result = Test(
|
||||
path: path,
|
||||
result: if path.extractFilename in exceptions: TestResult.Skip
|
||||
else: TestResult.Unstarted,
|
||||
expectedOutput: compileExpectedOutput(source),
|
||||
expectedError: compileExpectedError(source)
|
||||
)
|
||||
|
||||
proc buildTests(testDir: string): seq[Test] =
|
||||
for candidateObj in walkDir(testDir):
|
||||
let candidate = candidateObj.path
|
||||
if dirExists(candidate):
|
||||
log(LogLevel.Debug, &"Descending into dir {candidate}")
|
||||
result &= buildTests(candidate)
|
||||
else:
|
||||
result.add buildTest(candidate)
|
||||
|
||||
proc runTest(test: Test, runner: string) =
|
||||
log(LogLevel.Debug, &"Starting test {test.path}.")
|
||||
let process = startProcess(runner, args = @[test.path])
|
||||
test.process = process
|
||||
test.result = TestResult.Running
|
||||
|
||||
proc tryFinishTest(test: Test): bool =
|
||||
if test.process.running():
|
||||
return false
|
||||
test.output = test.process.outputStream.readAll()
|
||||
test.error = test.process.errorStream.readAll()
|
||||
if test.process.peekExitCode() == 0:
|
||||
test.result = TestResult.ToEval
|
||||
else:
|
||||
test.result = TestResult.Crash
|
||||
test.process.close()
|
||||
log(LogLevel.Debug, &"Test {test.path} finished.")
|
||||
return true
|
||||
|
||||
const maxAliveTests = 8
|
||||
const testWait = 10
|
||||
|
||||
proc runTests(tests: seq[Test], runner: string) =
|
||||
var
|
||||
aliveTests = 0
|
||||
currentTest = 0
|
||||
finishedTests = 0
|
||||
buffer = newBuffer()
|
||||
let totalTests = tests.len()
|
||||
|
||||
buffer.updateProgressBar(&"", totalTests, finishedTests)
|
||||
buffer.render()
|
||||
while aliveTests > 0 or currentTest < tests.len():
|
||||
buffer.render()
|
||||
sleep(testWait)
|
||||
if aliveTests < maxAliveTests and currentTest < tests.len():
|
||||
if tests[currentTest].result == TestResult.Unstarted:
|
||||
tests[currentTest].runTest(runner)
|
||||
inc aliveTests
|
||||
inc currentTest
|
||||
else:
|
||||
inc currentTest
|
||||
inc finishedTests
|
||||
for i in countup(0, min(currentTest, tests.high())):
|
||||
if tests[i].result == TestResult.Running:
|
||||
if tryFinishTest(tests[i]):
|
||||
inc finishedTests
|
||||
buffer.updateProgressBar(&"", totalTests, finishedTests)
|
||||
dec aliveTests
|
||||
else:
|
||||
inc tests[i].cycles
|
||||
buffer.render()
|
||||
|
||||
proc evalTest(test: Test) =
|
||||
test.output = test.output.strip()
|
||||
test.error = test.error.strip()
|
||||
test.expectedOutput = test.expectedOutput.strip()
|
||||
test.expectedError = test.expectedError.strip()
|
||||
if test.output != test.expectedOutput or test.error != test.expectedError:
|
||||
test.result = TestResult.Mismatch
|
||||
else:
|
||||
test.result = TestResult.Success
|
||||
|
||||
proc evalTests(tests: seq[Test]) =
|
||||
for test in tests:
|
||||
if test.result == TestResult.ToEval:
|
||||
evalTest(test)
|
||||
|
||||
proc printResults(tests: seq[Test]) =
|
||||
var
|
||||
skipped = 0
|
||||
success = 0
|
||||
fail = 0
|
||||
crash = 0
|
||||
|
||||
for test in tests:
|
||||
log(LogLevel.Debug, &"Test {test.path} result: {test.result}")
|
||||
case test.result:
|
||||
of TestResult.Skip:
|
||||
inc skipped
|
||||
of TestResult.Mismatch:
|
||||
inc fail
|
||||
log(LogLevel.Debug, &"[{test.path}\noutput:\n{test.output}\nerror:\n{test.error}\nexpected output:\n{test.expectedOutput}\nexpectedError:\n{test.expectedError}\n]")
|
||||
of TestResult.Crash:
|
||||
inc crash
|
||||
log(LogLevel.Debug, &"{test.path} \ncrash:\n{test.error}")
|
||||
of TestResult.Success:
|
||||
inc success
|
||||
else:
|
||||
log(LogLevel.Error, &"Probably a testing suite bug: test {test.path} has result {test.result}")
|
||||
let finalLevel = if fail == 0 and crash == 0: LogLevel.Info else: LogLevel.Error
|
||||
log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.")
|
||||
import os, strformat, parseopt, strutils
|
||||
|
||||
when isMainModule:
|
||||
const jatsVersion = "(dev)"
|
||||
|
||||
if paramCount() > 0:
|
||||
if paramStr(1) == "-h":
|
||||
echo "Usage: jats [-h | -v | -i | -o filename.txt]"
|
||||
quit(0)
|
||||
elif paramStr(1) == "-v":
|
||||
echo "JATS v" & $jatsVersion
|
||||
quit(0)
|
||||
var optparser = initOptParser(commandLineParams())
|
||||
type Action {.pure.} = enum
|
||||
Run, Help, Version
|
||||
var action: Action = Action.Run
|
||||
type DebugAction {.pure.} = enum
|
||||
Interactive, Stdout
|
||||
var debugActions: seq[DebugAction]
|
||||
var targetFiles: seq[string]
|
||||
var verbose = true
|
||||
|
||||
log(LogLevel.Debug, &"Welcome to JATS")
|
||||
type QuitValue {.pure.} = enum
|
||||
Success, Failure, ArgParseErr, InternalErr
|
||||
var quitVal = QuitValue.Success
|
||||
|
||||
runNimTests()
|
||||
var jatr = "jatr"
|
||||
var testDir = "japl"
|
||||
if not fileExists(jatr) and fileExists("tests" / jatr):
|
||||
log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths")
|
||||
jatr = "tests" / jatr
|
||||
testDir = "tests" / testDir
|
||||
proc evalKey(key: string) =
|
||||
let key = key.toLower()
|
||||
if key == "h" or key == "help":
|
||||
action = Action.Help
|
||||
elif key == "v" or key == "version":
|
||||
action = Action.Version
|
||||
elif key == "i" or key == "interactive":
|
||||
debugActions.add(DebugAction.Interactive)
|
||||
elif key == "s" or key == "silent":
|
||||
verbose = false
|
||||
elif key == "stdout":
|
||||
debugActions.add(DebugAction.Stdout)
|
||||
else:
|
||||
echo &"Unknown flag: {key}"
|
||||
action = Action.Help
|
||||
quitVal = QuitValue.ArgParseErr
|
||||
|
||||
log(LogLevel.Info, &"Running JAPL tests.")
|
||||
log(LogLevel.Info, &"Building tests...")
|
||||
let tests: seq[Test] = buildTests(testDir)
|
||||
log(LogLevel.Debug, &"Tests built.")
|
||||
log(LogLevel.Info, &"Running tests...")
|
||||
tests.runTests(jatr)
|
||||
log(LogLevel.Debug, &"Tests ran.")
|
||||
log(LogLevel.Debug, &"Evaluating tests...")
|
||||
tests.evalTests()
|
||||
log(LogLevel.Debug, &"Tests evaluated.")
|
||||
tests.printResults()
|
||||
log(LogLevel.Debug, &"Quitting JATS.")
|
||||
proc evalKeyVal(key: string, val: string) =
|
||||
let key = key.toLower()
|
||||
if key == "o" or key == "output":
|
||||
targetFiles.add(val)
|
||||
else:
|
||||
echo &"Unknown option: {key}"
|
||||
action = Action.Help
|
||||
quitVal = QuitValue.ArgParseErr
|
||||
|
||||
# special options to view the entire debug log
|
||||
proc evalArg(key: string) =
|
||||
echo &"Unexpected argument"
|
||||
action = Action.Help
|
||||
quitVal = QuitValue.ArgParseErr
|
||||
|
||||
if paramCount() > 0:
|
||||
if paramStr(1) == "-i":
|
||||
writeFile("testresults.txt", getTotalLog())
|
||||
discard execShellCmd("less testresults.txt")
|
||||
removeFile("testresults.txt")
|
||||
if paramStr(1) == "-o":
|
||||
writeFile(paramStr(2), getTotalLog())
|
||||
while true:
|
||||
optparser.next()
|
||||
case optparser.kind:
|
||||
of cmdEnd: break
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if optparser.val == "":
|
||||
evalKey(optparser.key)
|
||||
else:
|
||||
evalKeyVal(optparser.key, optparser.val)
|
||||
of cmdArgument:
|
||||
evalArg(optparser.key)
|
||||
|
||||
proc printUsage =
|
||||
echo """
|
||||
JATS - Just Another Test Suite
|
||||
|
||||
Usage:
|
||||
jats
|
||||
Runs the tests
|
||||
Flags:
|
||||
-i (or --interactive) displays all debug info
|
||||
-o:<filename> (or --output:<filename>) saves debug info to a file
|
||||
-s (or --silent) will disable all output (except --stdout)
|
||||
--stdout will put all debug info to stdout
|
||||
-h (or --help) displays this help message
|
||||
-v (or --version) displays the version number of JATS
|
||||
"""
|
||||
proc printVersion =
|
||||
echo &"JATS - Just Another Test Suite version {jatsVersion}"
|
||||
|
||||
if action == Action.Help:
|
||||
printUsage()
|
||||
quit int(quitVal)
|
||||
elif action == Action.Version:
|
||||
printVersion()
|
||||
quit int(quitVal)
|
||||
elif action == Action.Run:
|
||||
discard
|
||||
else:
|
||||
echo &"Unknown action {action}, please contact the devs to fix this."
|
||||
quit int(QuitValue.InternalErr)
|
||||
|
||||
setVerbosity(verbose)
|
||||
setLogfiles(targetFiles)
|
||||
|
||||
# start of JATS
|
||||
|
||||
try:
|
||||
log(LogLevel.Debug, &"Welcome to JATS")
|
||||
|
||||
runNimTests()
|
||||
var jatr = "jatr"
|
||||
var testDir = "japl"
|
||||
if not fileExists(jatr) and fileExists("tests" / jatr):
|
||||
log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths")
|
||||
jatr = "tests" / jatr
|
||||
testDir = "tests" / testDir
|
||||
|
||||
log(LogLevel.Info, &"Running JAPL tests.")
|
||||
log(LogLevel.Info, &"Building tests...")
|
||||
let tests: seq[Test] = buildTests(testDir)
|
||||
log(LogLevel.Debug, &"Tests built.")
|
||||
log(LogLevel.Info, &"Running tests...")
|
||||
tests.runTests(jatr)
|
||||
log(LogLevel.Debug, &"Tests ran.")
|
||||
log(LogLevel.Debug, &"Evaluating tests...")
|
||||
tests.evalTests()
|
||||
log(LogLevel.Debug, &"Tests evaluated.")
|
||||
if not tests.printResults():
|
||||
quitVal = QuitValue.Failure
|
||||
|
||||
log(LogLevel.Debug, &"Quitting JATS.")
|
||||
|
||||
# special options to view the entire debug log
|
||||
finally:
|
||||
let logs = getTotalLog()
|
||||
for action in debugActions:
|
||||
case action:
|
||||
of DebugAction.Interactive:
|
||||
let lessExe = findExe("less", extensions = @[""])
|
||||
let moreExe = findExe("more", extensions = @[""])
|
||||
var viewer = if lessExe == "": moreExe else: lessExe
|
||||
if viewer != "":
|
||||
writeFile("testresults.txt", logs) # yes, testresults.txt is reserved
|
||||
discard execShellCmd(viewer & " testresults.txt") # this way because of pipe buffer sizes
|
||||
removeFile("testresults.txt")
|
||||
else:
|
||||
write stderr, "Interactive mode not supported on your platform, try --stdout and piping, or install/alias 'more' or 'less' to a terminal pager.\n"
|
||||
of DebugAction.Stdout:
|
||||
echo logs
|
||||
|
||||
quit int(quitVal)
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# logging stuff
|
||||
|
||||
import terminal, strformat, times, strutils
|
||||
|
||||
type LogLevel* {.pure.} = enum
|
||||
Debug, # always written to file only (large outputs, such as the entire output of the failing test or stacktrace)
|
||||
Info, # important information about the progress of the test suite
|
||||
Error, # failing tests (printed with red)
|
||||
Stdout, # always printed to stdout only (for cli experience)
|
||||
|
||||
|
||||
const echoedLogs = {LogLevel.Info, LogLevel.Error, LogLevel.Stdout}
|
||||
const echoedLogsSilent = {LogLevel.Error}
|
||||
const savedLogs = {LogLevel.Debug, LogLevel.Info, LogLevel.Error}
|
||||
|
||||
const logColors = [LogLevel.Debug: fgDefault, LogLevel.Info: fgGreen, LogLevel.Error: fgRed, LogLevel.Stdout: fgYellow]
|
||||
|
||||
var totalLog = ""
|
||||
var verbose = true
|
||||
var logfiles: seq[string]
|
||||
proc setVerbosity*(verb: bool) =
|
||||
verbose = verb
|
||||
|
||||
proc log*(level: LogLevel, msg: string) =
|
||||
let msg = &"[{$level} - {$getTime()}] {msg}"
|
||||
if level in savedLogs:
|
||||
totalLog &= msg & "\n"
|
||||
if logfiles.len() > 0:
|
||||
for file in logfiles:
|
||||
let handle = file.open(fmAppend)
|
||||
handle.writeLine(msg)
|
||||
handle.close()
|
||||
if (verbose and (level in echoedLogs)) or ((not verbose) and (level in echoedLogsSilent)):
|
||||
setForegroundColor(logColors[level])
|
||||
echo msg
|
||||
setForegroundColor(fgDefault)
|
||||
|
||||
proc getTotalLog*: string =
|
||||
totalLog
|
||||
|
||||
const progbarLength = 25
|
||||
type Buffer* = ref object
|
||||
contents: string
|
||||
previous: string
|
||||
|
||||
proc newBuffer*: Buffer =
|
||||
hideCursor()
|
||||
new(result)
|
||||
|
||||
proc updateProgressBar*(buf: Buffer, text: string, total: int, current: int) =
|
||||
var newline = ""
|
||||
newline &= "["
|
||||
let ratio = current / total
|
||||
let filledCount = int(ratio * progbarLength)
|
||||
if filledCount > 0:
|
||||
newline &= "=".repeat(filledCount)
|
||||
if filledCount < progbarLength:
|
||||
newline &= " ".repeat(progbarLength - filledCount - 1)
|
||||
newline &= &"] ({current}/{total}) {text}"
|
||||
# to avoid process switching during half-written progress bars and whatnot all terminal editing happens at the end
|
||||
let w = terminalWidth()
|
||||
if w > newline.len():
|
||||
newline &= " ".repeat(w - newline.len() - 1)
|
||||
buf.contents = newline
|
||||
|
||||
proc clearLineAndWrite(text: string, oldsize: int) =
|
||||
write stdout, text & "\r"
|
||||
|
||||
proc render*(buf: Buffer) =
|
||||
if verbose: #and buf.previous != buf.contents:
|
||||
clearLineAndWrite(buf.contents, buf.previous.len())
|
||||
buf.previous = buf.contents
|
||||
|
||||
proc endBuffer*(buf: Buffer) =
|
||||
showCursor()
|
||||
|
||||
proc setLogfiles*(files: seq[string]) =
|
||||
logfiles = files
|
|
@ -1,5 +1,5 @@
|
|||
import multibyte
|
||||
import ../testutils
|
||||
import ../logutils
|
||||
|
||||
proc runNimTests* =
|
||||
log(LogLevel.Info, "Running nim tests.")
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Test object and helpers
|
||||
|
||||
import re, strutils, osproc
|
||||
|
||||
# types
|
||||
|
||||
type
|
||||
TestResult* {.pure.} = enum
|
||||
Unstarted, Running, ToEval, Success, Skip, Mismatch, Crash
|
||||
|
||||
Test* = ref object
|
||||
result*: TestResult
|
||||
path*: string
|
||||
expectedOutput*: string
|
||||
expectedError*: string
|
||||
input*: string
|
||||
output*: string
|
||||
error*: string
|
||||
process*: Process
|
||||
cycles*: int
|
||||
|
||||
# parsing the test notation
|
||||
|
||||
proc compileExpectedOutput*(source: string): string =
|
||||
for line in source.split('\n'):
|
||||
if line =~ re"^.*//stdout:[ ]?(.*)$":
|
||||
result &= matches[0] & "\n"
|
||||
|
||||
proc compileExpectedError*(source: string): string =
|
||||
for line in source.split('\n'):
|
||||
if line =~ re"^.*//stderr:[ ]?(.*)$":
|
||||
result &= matches[0] & "\n"
|
||||
|
||||
proc compileInput*(source: string): string =
|
||||
for line in source.split('\n'):
|
||||
if line =~ re"^.*//stdin:[ ]?(.*)$":
|
||||
result &= matches[0] & "\n"
|
||||
|
||||
|
||||
# stuff for cleaning test output
|
||||
|
||||
proc tuStrip*(input: string): string =
|
||||
return input.replace(re"[\n\r]*$", "")
|
||||
|
|
@ -1,92 +1,162 @@
|
|||
# Common code from between the JAPL testing suites
|
||||
# (during transition from runtests -> Just Another Test Runner
|
||||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re, strutils, terminal, osproc, strformat, times, os
|
||||
# Test object helpers
|
||||
|
||||
# types
|
||||
import testobject, logutils, os, osproc, streams, strformat
|
||||
|
||||
type
|
||||
TestResult* {.pure.} = enum
|
||||
Unstarted, Running, ToEval, Success, Skip, Mismatch, Crash
|
||||
# Tests that represent not-yet implemented behaviour
|
||||
const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl", "problem4.jpl"]
|
||||
# TODO: for_with_function.jpl and problem4.jpl should already be implemented, check on them
|
||||
|
||||
Test* = ref object
|
||||
result*: TestResult
|
||||
path*: string
|
||||
expectedOutput*: string
|
||||
expectedError*: string
|
||||
output*: string
|
||||
error*: string
|
||||
process*: Process
|
||||
cycles*: int
|
||||
proc buildTest(path: string): Test =
|
||||
log(LogLevel.Debug, &"Building test {path}")
|
||||
let source = readFile(path)
|
||||
result = Test(
|
||||
path: path,
|
||||
result: if path.extractFilename in exceptions: TestResult.Skip
|
||||
else: TestResult.Unstarted,
|
||||
expectedOutput: compileExpectedOutput(source),
|
||||
expectedError: compileExpectedError(source),
|
||||
input: compileInput(source)
|
||||
)
|
||||
|
||||
# logging stuff
|
||||
proc buildTests*(testDir: string): seq[Test] =
|
||||
for candidateObj in walkDir(testDir):
|
||||
let candidate = candidateObj.path
|
||||
if dirExists(candidate):
|
||||
log(LogLevel.Debug, &"Descending into dir {candidate}")
|
||||
result &= buildTests(candidate)
|
||||
else:
|
||||
result.add buildTest(candidate)
|
||||
|
||||
type LogLevel* {.pure.} = enum
|
||||
Debug, # always written to file only (large outputs, such as the entire output of the failing test or stacktrace)
|
||||
Info, # important information about the progress of the test suite
|
||||
Error, # failing tests (printed with red)
|
||||
Stdout, # always printed to stdout only (for cli experience)
|
||||
proc runTest(test: Test, runner: string) =
|
||||
log(LogLevel.Debug, &"Starting test {test.path}.")
|
||||
let process = startProcess(runner, args = @[test.path])
|
||||
test.process = process
|
||||
if test.input.len() > 0:
|
||||
var f: File
|
||||
let suc = f.open(process.inputHandle, fmWrite)
|
||||
if suc:
|
||||
f.write(test.input)
|
||||
else:
|
||||
log(LogLevel.Error, &"Stdin File handle could not be opened for test {test.path}")
|
||||
test.result = Crash
|
||||
|
||||
test.result = TestResult.Running
|
||||
|
||||
proc tryFinishTest(test: Test): bool =
|
||||
if test.process.running():
|
||||
return false
|
||||
test.output = test.process.outputStream.readAll()
|
||||
test.error = test.process.errorStream.readAll()
|
||||
if test.process.peekExitCode() == 0:
|
||||
test.result = TestResult.ToEval
|
||||
else:
|
||||
test.result = TestResult.Crash
|
||||
test.process.close()
|
||||
log(LogLevel.Debug, &"Test {test.path} finished.")
|
||||
return true
|
||||
|
||||
proc killTest(test: Test) =
|
||||
if test.process.running():
|
||||
test.process.kill()
|
||||
discard test.process.waitForExit()
|
||||
log(LogLevel.Error, &"Test {test.path} was killed for taking too long.")
|
||||
discard test.tryFinishTest()
|
||||
|
||||
const maxAliveTests = 16
|
||||
const testWait = 100
|
||||
const timeout = 100 # number of cycles after which a test is killed for timeout
|
||||
|
||||
proc runTests*(tests: seq[Test], runner: string) =
|
||||
var
|
||||
aliveTests = 0
|
||||
currentTest = 0
|
||||
finishedTests = 0
|
||||
buffer = newBuffer()
|
||||
let totalTests = tests.len()
|
||||
|
||||
buffer.updateProgressBar(&"", totalTests, finishedTests)
|
||||
buffer.render()
|
||||
while aliveTests > 0 or currentTest < tests.len():
|
||||
buffer.render()
|
||||
sleep(testWait)
|
||||
if aliveTests < maxAliveTests and currentTest < tests.len():
|
||||
if tests[currentTest].result == TestResult.Unstarted:
|
||||
tests[currentTest].runTest(runner)
|
||||
inc aliveTests
|
||||
inc currentTest
|
||||
else:
|
||||
inc currentTest
|
||||
inc finishedTests
|
||||
for i in countup(0, min(currentTest, tests.high())):
|
||||
if tests[i].result == TestResult.Running:
|
||||
if tryFinishTest(tests[i]):
|
||||
inc finishedTests
|
||||
buffer.updateProgressBar(&"Finished {tests[i].path}.", totalTests, finishedTests)
|
||||
dec aliveTests
|
||||
elif tests[i].cycles >= timeout:
|
||||
tests[i].killTest()
|
||||
inc finishedTests
|
||||
dec aliveTests
|
||||
buffer.updateProgressBar(&"Killed {tests[i].path}.", totalTests, finishedTests)
|
||||
else:
|
||||
inc tests[i].cycles
|
||||
buffer.render()
|
||||
buffer.endBuffer()
|
||||
|
||||
proc evalTest(test: Test) =
|
||||
test.output = test.output.tuStrip()
|
||||
test.error = test.error.tuStrip()
|
||||
test.expectedOutput = test.expectedOutput.tuStrip()
|
||||
test.expectedError = test.expectedError.tuStrip()
|
||||
if test.output != test.expectedOutput or test.error != test.expectedError:
|
||||
test.result = TestResult.Mismatch
|
||||
else:
|
||||
test.result = TestResult.Success
|
||||
|
||||
proc evalTests*(tests: seq[Test]) =
|
||||
for test in tests:
|
||||
if test.result == TestResult.ToEval:
|
||||
evalTest(test)
|
||||
|
||||
proc printResults*(tests: seq[Test]): bool =
|
||||
var
|
||||
skipped = 0
|
||||
success = 0
|
||||
fail = 0
|
||||
crash = 0
|
||||
|
||||
for test in tests:
|
||||
log(LogLevel.Debug, &"Test {test.path} result: {test.result}")
|
||||
case test.result:
|
||||
of TestResult.Skip:
|
||||
inc skipped
|
||||
of TestResult.Mismatch:
|
||||
inc fail
|
||||
log(LogLevel.Debug, &"[{test.path}\noutput:\n{test.output}\nerror:\n{test.error}\nexpected output:\n{test.expectedOutput}\nexpectedError:\n{test.expectedError}\n]")
|
||||
of TestResult.Crash:
|
||||
inc crash
|
||||
log(LogLevel.Debug, &"{test.path} \ncrash:\n{test.error}")
|
||||
of TestResult.Success:
|
||||
inc success
|
||||
else:
|
||||
log(LogLevel.Error, &"Probably a testing suite bug: test {test.path} has result {test.result}")
|
||||
let finalLevel = if fail == 0 and crash == 0: LogLevel.Info else: LogLevel.Error
|
||||
log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.")
|
||||
|
||||
fail == 0 and crash == 0
|
||||
|
||||
|
||||
const echoedLogs = {LogLevel.Info, LogLevel.Error, LogLevel.Stdout}
|
||||
const savedLogs = {LogLevel.Debug, LogLevel.Info, LogLevel.Error}
|
||||
|
||||
const logColors = [LogLevel.Debug: fgDefault, LogLevel.Info: fgGreen, LogLevel.Error: fgRed, LogLevel.Stdout: fgYellow]
|
||||
|
||||
var totalLog = ""
|
||||
|
||||
proc log*(level: LogLevel, msg: string) =
|
||||
let msg = &"[{$level} - {$getTime()}] {msg}"
|
||||
if level in savedLogs:
|
||||
totalLog &= msg & "\n"
|
||||
if level in echoedLogs:
|
||||
setForegroundColor(logColors[level])
|
||||
echo msg
|
||||
setForegroundColor(fgDefault)
|
||||
|
||||
proc getTotalLog*: string =
|
||||
totalLog
|
||||
|
||||
const progbarLength = 25
|
||||
type Buffer* = ref object
|
||||
contents: string
|
||||
previous: string
|
||||
|
||||
proc newBuffer*: Buffer =
|
||||
new(result)
|
||||
|
||||
proc updateProgressBar*(buf: Buffer, text: string, total: int, current: int) =
|
||||
var newline = ""
|
||||
newline &= "["
|
||||
let ratio = current / total
|
||||
let filledCount = int(ratio * progbarLength)
|
||||
for i in countup(1, filledCount):
|
||||
newline &= "="
|
||||
for i in countup(filledCount + 1, progbarLength):
|
||||
newline &= " "
|
||||
newline &= &"] ({current}/{total}) {text}"
|
||||
# to avoid process switching during half-written progress bars and whatnot all terminal editing happens at the end
|
||||
buf.contents = newline
|
||||
|
||||
proc render*(buf: Buffer) =
|
||||
if buf.previous != buf.contents:
|
||||
echo buf.contents
|
||||
buf.previous = buf.contents
|
||||
|
||||
# parsing the test notation
|
||||
|
||||
proc compileExpectedOutput*(source: string): string =
|
||||
for line in source.split('\n'):
|
||||
if line =~ re"^.*//output:[ ]?(.*)$":
|
||||
result &= matches[0] & "\n"
|
||||
|
||||
proc compileExpectedError*(source: string): string =
|
||||
for line in source.split('\n'):
|
||||
if line =~ re"^.*//error:[ ]?(.*)$":
|
||||
result &= matches[0] & "\n"
|
||||
|
||||
# stuff for cleaning test output
|
||||
|
||||
proc strip*(input: string): string =
|
||||
return input.replace(re"[\n\r]*$", "")
|
||||
|
||||
|
|
Loading…
Reference in New Issue