Merge pull request #28 from Productive2/master

Testing suite + debugger bugs + some binary operator fixes
This commit is contained in:
Mattia 2021-01-05 14:56:44 +01:00 committed by GitHub
commit aaf2e66528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 269 additions and 20 deletions

6
.gitignore vendored
View File

@ -11,6 +11,8 @@ htmldocs/
# JAPL
src/japl
src/compiler
tests/runtests
# MacOS
@ -29,3 +31,7 @@ main
# JAPL build config file
config.nim
# test results
testresults.txt

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python3
# Copyright 2020 Mattia Giambirtone
#

View File

@ -72,6 +72,7 @@ type
Term,
Factor,
Unary,
Exponentiation,
Call,
Primary
@ -1095,7 +1096,7 @@ var rules: array[TokenType, ParseRule] = [
makeRule(nil, binary, Precedence.Comparison), # GE
makeRule(nil, binary, Precedence.Comparison), # LE
makeRule(nil, binary, Precedence.Factor), # MOD
makeRule(nil, binary, Precedence.Factor), # POW
makeRule(nil, binary, Precedence.Exponentiation), # POW
makeRule(nil, binary, Precedence.Comparison), # GT
makeRule(grouping, call, Precedence.Call), # LP
makeRule(nil, nil, Precedence.None), # RP

View File

@ -79,10 +79,11 @@ const simpleInstructions* = {OpCode.Return, OpCode.Add, OpCode.Multiply,
OpCode.Inf, OpCode.Shl, OpCode.Shr,
OpCode.Xor, OpCode.Not, OpCode.Equal,
OpCode.Greater, OpCode.Less, OpCode.GetItem,
OpCode.Slice, OpCode.Pop, OpCode.DefineGlobal,
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}
const constantInstructions* = {OpCode.Constant}
OpCode.Slice, OpCode.Pop, OpCode.Negate}
const constantInstructions* = {OpCode.Constant, OpCode.DefineGlobal,
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}
const constantLongInstructions* = {OpCode.ConstantLong}
const byteInstructions* = {OpCode.SetLocal, OpCode.GetLocal, OpCode.DeleteLocal,
OpCode.Call}

View File

@ -31,8 +31,12 @@ proc natPrint(args: seq[ptr Obj]): tuple[ok: bool, result: ptr Obj] =
## is passed, they will be printed separated
## by a space
var res = ""
for arg in args:
res = res & arg.stringify() & " "
for i in countup(0, args.high()):
let arg = args[i]
if i < args.high():
res = res & arg.stringify() & " "
else:
res = res & arg.stringify()
echo res
return (ok: true, result: asNil())

View File

@ -30,7 +30,7 @@ proc simpleInstruction(name: string, index: int): int =
proc byteInstruction(name: string, chunk: Chunk, offset: int): int =
var slot = chunk.code[offset + 1]
echo &"\tInstruction at IP: {name}, points to slot {slot}\n"
return offset + 1
return offset + 2
proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =

34
tests/japl/arithmetic.jpl Normal file
View File

@ -0,0 +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

21
tests/japl/booleans.jpl Normal file
View File

@ -0,0 +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(!0);//output:true
print(!1);//output:false
print(!1 and !2);//output:false
print(!(1 and 0));//output:true

6
tests/japl/for.jpl Normal file
View File

@ -0,0 +1,6 @@
for (var x = 0; x < 2; x = x + 1)
{
print(x);
//output:0
//output:1
}

28
tests/japl/if.jpl Normal file
View File

@ -0,0 +1,28 @@
var x = 5;
if (x > 2)
{
print("large");//output:large
}
else
{
print("smol");//won't run
}
if (x < 4)
{
print("large");//won't run
}
print("beep");//output:beep
if (x == 5)
print("equal to 5");//output:equal to 5
if (2 != x)
print("not 2");//output:not 2
else
print("2");
if (2 == x)
print("2");
else
print("not 2");//output:not 2

30
tests/japl/vars.jpl Normal file
View File

@ -0,0 +1,30 @@
var x = 1;
var y = 2;
print(x);//output:1
print(y);//output:2
{
var x = 4;
var y = 5;
print(x);//output:4
print(y);//output:5
{
var z = 6;
var y = 2;
print(x);//output:4
print(y);//output:2
}
print(x);//output:4
print(y);//output:5
}
print(x);//output:1
print(y);//output:2
var longName;
print(longName); //output:nil
longName = 5;
print(longName); //output:5
longName = "hello";
print(longName); //output:hello
longName = longName + " world";
print(longName); //output:hello world

20
tests/japl/while.jpl Normal file
View File

@ -0,0 +1,20 @@
var x = 5;
while (x > 0)
{
x = x - 1;
print(x);
//output:4
//output:3
//output:2
//output:1
//output:0
}
var string = "h";
while (x < 10)
{
x = x + 1;
string = string + "A";
}
print(string);//output:hAAAAAAAAAA

7
tests/multibyte.nim Normal file
View File

@ -0,0 +1,7 @@
import ../src/multibyte
template testMultibyte* =
for i in countup(0, int(uint16.high())):
assert fromDouble(toDouble(i)) == uint16(i)

View File

@ -1,6 +0,0 @@
import ../../src/multibyte
for i in countup(0, int(uint16.high())):
assert fromDouble(toDouble(i)) == uint16(i)

View File

@ -1,5 +0,0 @@
# temporary nim file so I can see if the stuff I've touched compiles :'D
import ..meta/opcode
import ..types/jobject
import ..util/debug

102
tests/runtests.nim Normal file
View File

@ -0,0 +1,102 @@
# Common entry point to run JAPL's tests
#
# - Assumes "japl" binary in ../src/japl built with all debugging off
# - Goes through all tests in (/tests/)
# - Runs all tests in (/tests/)japl/ and checks their output (marked by `//output:{output}`)
#
# go through all nim tests
import multibyte
testMultibyte()
# go through all japl tests
import os, strformat, times, re
proc compileExpectedOutput(path: string): string =
for line in path.lines():
if line =~ re"^.*//output:(.*)$":
result &= matches[0] & "\n"
proc deepComp(left, right: string): tuple[same: bool, place: int] =
result.same = true
if left.high() != right.high():
result.same = false
for i in countup(0, left.high()):
result.place = i
if i > right.high():
# already false bc of the len check at the beginning
# already correct place bc it's updated every i
return
if left[i] != right[i]:
result.same = false
return
var testsDir = "tests" / "japl"
var japlExec = "src" / "japl"
# support running from both the japl root and the tests dir where it
# resides
var currentDir = getCurrentDir()
if currentDir.lastPathPart() == "tests":
testsDir = "japl"
japlExec = ".." / japlExec
let testResultsFile = open("testresults.txt", fmAppend)
testResultsFile.writeLine(&"Executing tests at {$getTime()}")
# quick logging levels using procs
proc log(msg: string) =
testResultsFile.File.writeLine(&"[LOG] {msg}")
echo msg
proc detail(msg: string) =
testResultsFile.writeLine(&"[DETAIL] {msg}")
# Exceptions for tests that represent not-yet implemented behaviour
var exceptions = @["all.jpl"]
log &"Running JAPL tests"
log &"Looking for JAPL tests in {testsDir}"
log &"Looking for JAPL executable at {japlExec}"
if not fileExists(japlExec):
log &"JAPL executable not found"
quit(1)
if not dirExists(testsDir):
log &"Tests dir not found"
quit(1)
for file in walkDir(testsDir):
block singularTest:
for exc in exceptions:
if exc == file.path.extractFilename:
log &"Skipping {file.path} because it's on the exceptions list"
break singularTest
log &"Running test {file.path}"
if fileExists("testoutput.txt"):
removeFile("testoutput.txt") # in case this crashed
discard execShellCmd(&"{japlExec} {file.path} >>testoutput.txt")
let expectedOutput = compileExpectedOutput(file.path).replace(re"(\n*)$", "")
let realOutputFile = open("testoutput.txt", fmRead)
let realOutput = realOutputFile.readAll().replace(re"([\n\r]*)$", "")
realOutputFile.close()
removeFile("testoutput.txt")
let comparison = deepComp(expectedOutput, realOutput)
if comparison.same:
log &"Successful test {file.path}"
else:
detail &"Expected output:\n{expectedOutput}\n"
detail &"Received output:\n{realOutput}\n"
detail &"Mismatch at pos {comparison.place}"
if comparison.place > expectedOutput.high() or
comparison.place > realOutput.high():
detail &"Length mismatch"
else:
detail &"Expected is '{expectedOutput[comparison.place]}' while received '{realOutput[comparison.place]}'"
log &"Test failed {file.path}, check 'testresults.txt' for details"
testResultsFile.close()