Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Productive2 2021-02-26 15:13:20 +01:00
commit 960ba879cb
12 changed files with 191 additions and 89 deletions

26
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,26 @@
# Automatically runs tests
name: Run tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v2.2.1
with:
# We test using a reasonably modern Python version
python-version: 3.8.0
architecture: x64
- uses: actions/checkout@v2
- name: Run production-mode tests
run: ./build.py --profile resources/profiles/production.json
- name: Run developmet tests
run: ./build.py --profile resources/profiles/production.json

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -148,7 +148,7 @@ def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dic
listing = "\n- {} = {}"
if not os.path.exists(path):
logging.error(f"Input path '{path}' does not exist")
return
return False
if os.path.isfile(config_path) and not override:
logging.warning(f"A config file exists at '{config_path}', keeping it")
else:
@ -159,7 +159,7 @@ def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dic
build_config.write(CONFIG_TEMPLATE.format(**options))
except Exception as fatal:
logging.error(f"A fatal unhandled exception occurred -> {type(fatal).__name__}: {fatal}")
return
return False
else:
logging.debug(f"Config file has been generated, compiling with options as follows: {''.join(listing.format(k, v) for k, v in options.items())}")
logging.debug(f"Nim compiler options: {''.join(listing.format(k, v) for k, v in flags.items())}")
@ -186,22 +186,22 @@ def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dic
_, stderr, status = run_command(command, stdout=DEVNULL, stderr=PIPE)
if status != 0:
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
else:
command = f"nim compile --opt:speed {tests_path}"
_, stderr, status = run_command(command, stdout=DEVNULL, stderr=PIPE)
if status != 0:
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
else:
logging.debug(f"Test suite compilation completed in {time() - start:.2f} seconds")
logging.debug("Running tests")
start = time()
# TODO: Find a better way of running the test suite
process = run_command(f"{tests_path}", mode="run", shell=True, stderr=PIPE)
if status != 0:
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
else:
logging.debug(f"Test suite ran in {time() - start:.2f} seconds")
logging.info("Test suite completed!")
return False
command = f"nim compile --opt:speed {tests_path}"
_, stderr, status = run_command(command, stdout=DEVNULL, stderr=PIPE)
if status != 0:
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
return False
logging.debug(f"Test suite compilation completed in {time() - start:.2f} seconds")
logging.debug("Running tests")
start = time()
# TODO: Find a better way of running the test suite
process = run_command(f"{tests_path} {'--stdout' if verbose else ''}", mode="run", shell=True, stderr=PIPE)
if status != 0:
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
return False
logging.debug(f"Test suite ran in {time() - start:.2f} seconds")
logging.info("Test suite completed!")
if args.install:
if os.name == "nt":
logging.warning("Sorry, but automatically installing JAPL is not yet supported on windows")
@ -220,14 +220,15 @@ def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dic
logging.debug(f"Path '{path}' is not writable, attempting next entry in PATH")
except Exception as fatal:
logging.error(f"A fatal unhandled exception occurred -> {type(fatal).__name__}: {fatal}")
else:
logging.debug(f"JAPL installed at '{path}', setting executable permissions")
# TODO: Use external oschmod library once we support windows!
try:
os.chmod(install_path, os.stat(install_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
except Exception as fatal:
logging.error(f"A fatal unhandled exception occurred -> {type(fatal).__name__}: {fatal}")
break
return False
logging.debug(f"JAPL installed at '{path}', setting executable permissions")
# TODO: Use external oschmod library once we support windows!
try:
os.chmod(install_path, os.stat(install_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
except Exception as fatal:
logging.error(f"A fatal unhandled exception occurred -> {type(fatal).__name__}: {fatal}")
break
return True
if __name__ == "__main__":
@ -310,14 +311,17 @@ if __name__ == "__main__":
exit()
else:
logging.info(f"Using profile '{args.profile}'")
build(args.path,
if build(args.path,
flags,
options,
args.override_config,
args.skip_tests,
args.install,
args.ignore_binary,
args.verbose)
logging.debug("Build tool exited")
args.verbose):
logging.debug("Build tool exited successfully")
else:
logging.debug("Build tool exited with error")
exit(1)
except KeyboardInterrupt:
logging.info("Interrupted by the user")

View File

@ -312,11 +312,11 @@ proc binary(self: Compiler, canAssign: bool) =
of TokenType.GT:
self.emitByte(OpCode.Greater)
of TokenType.GE:
self.emitBytes(OpCode.Less, OpCode.Not)
self.emitByte(OpCode.GreaterOrEqual)
of TokenType.LT:
self.emitByte(OpCode.Less)
of TokenType.LE:
self.emitBytes(OpCode.Greater, OpCode.Not)
self.emitByte(OpCode.LessOrEqual)
of TokenType.CARET:
self.emitByte(OpCode.Xor)
of TokenType.SHL:
@ -329,6 +329,8 @@ proc binary(self: Compiler, canAssign: bool) =
self.emitByte(OpCode.Band)
of TokenType.IS:
self.emitByte(OpCode.Is)
of TokenType.ISNOT:
self.emitBytes(OpCode.Is, Opcode.Not)
of TokenType.AS:
self.emitByte(OpCode.As)
else:
@ -991,7 +993,7 @@ proc statement(self: Compiler) =
## Parses statements
if self.parser.match(TokenType.FOR):
self.forStatement()
elif self.parser.match(IF):
elif self.parser.match(TokenType.IF):
self.ifStatement()
elif self.parser.match(TokenType.WHILE):
self.whileStatement()
@ -1102,7 +1104,8 @@ var rules: array[TokenType, ParseRule] = [
makeRule(unary, nil, Precedence.None), # TILDE
makeRule(nil, binary, Precedence.Is), # IS
makeRule(nil, binary, Precedence.As), # AS
makeRule(parseLambda, nil, Precedence.None) # LAMBDA
makeRule(parseLambda, nil, Precedence.None), # LAMBDA
makeRule(nil, binary, Precedence.Is), # ISNOT
]

View File

@ -58,7 +58,7 @@ const RESERVED = to_table({
"continue": TokenType.CONTINUE, "inf": TokenType.INF,
"nan": TokenType.NAN, "is": TokenType.IS,
"not": TokenType.NEG, "as": TokenType.AS,
"lambda": TokenType.LAMBDA})
"lambda": TokenType.LAMBDA, "isnot": TokenType.ISNOT})
type
Lexer* = ref object
source*: string

View File

@ -44,6 +44,8 @@ type
Greater,
Less,
Equal,
GreaterOrEqual,
LessOrEqual,
Not,
GetItem,
Slice,
@ -81,7 +83,7 @@ const simpleInstructions* = {OpCode.Return, OpCode.Add, OpCode.Multiply,
OpCode.Xor, OpCode.Not, OpCode.Equal,
OpCode.Greater, OpCode.Less, OpCode.GetItem,
OpCode.Slice, OpCode.Pop, OpCode.Negate,
OpCode.Is, OpCode.As}
OpCode.Is, OpCode.As, GreaterOrEqual, LessOrEqual}
const constantInstructions* = {OpCode.Constant, OpCode.DefineGlobal,
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}

View File

@ -29,7 +29,8 @@ type
WHILE, DEL, BREAK, EOF,
COLON, CONTINUE, CARET,
SHL, SHR, NAN, INF, BAND,
BOR, TILDE, IS, AS, LAMBDA
BOR, TILDE, IS, AS, LAMBDA,
ISNOT
Token* = object
kind*: TokenType
lexeme*: string

View File

@ -31,7 +31,7 @@ proc newFunction*(name: string = "", chunk: Chunk, arity: int = 0): ptr Function
## anonymous code object
# TODO: Add support for optional parameters
result = allocateObj(Function, ObjectType.Function)
if name.len > 1:
if name.len >= 1:
result.name = name.asStr()
else:
result.name = nil

View File

@ -391,7 +391,7 @@ proc binaryNot*(self: ptr Obj): returnType =
raise newException(NotImplementedError, "")
proc lt*(self: ptr Obj, other: ptr Obj): bool =
proc lt*(self: ptr Obj, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
## Returns the result of self < other or
## raises an error if the operation
## is unsupported
@ -406,7 +406,7 @@ proc lt*(self: ptr Obj, other: ptr Obj): bool =
raise newException(NotImplementedError, "")
proc gt*(self: ptr Obj, other: ptr Obj): bool =
proc gt*(self: ptr Obj, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
## Returns the result of self > other or
## raises an error if the operation
## is unsupported

View File

@ -133,50 +133,50 @@ proc eq*(self, other: ptr Infinity): bool =
result = self.isNegative == other.isNegative
proc lt*(self: ptr Infinity, other: ptr Obj): bool =
proc lt*(self: ptr Infinity, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
let other = cast[ptr Integer](other)
if self.isNegative and other.intValue > 0:
result = true
if self.isNegative:
result = (result: true, obj: other)
else:
result = false
result = (result: false, obj: nil)
of ObjectType.Float:
let other = cast[ptr Float](other)
if self.isNegative and other.floatValue > 0.0:
result = true
if self.isNegative:
result = (result: true, obj: other)
else:
result = false
result = (result: false, obj: nil)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative and not self.isNegative:
result = false
result = (result: true, obj: other)
else:
result = false
result = (result: false, obj: nil)
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Infinity, other: ptr Obj): bool =
proc gt*(self: ptr Infinity, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
let other = cast[ptr Integer](other)
if self.isNegative and other.intValue > 0:
result = false
if self.isNegative:
result = (result: false, obj: nil)
else:
result = true
result = (result: true, obj: other)
of ObjectType.Float:
let other = cast[ptr Float](other)
if self.isNegative and other.floatValue > 0.0:
result = false
if self.isNegative:
result = (result: false, obj: nil)
else:
result = true
result = (result: true, obj: other)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative and not self.isNegative:
result = true
result = (result: false, obj: nil)
else:
result = false
result = (result: true, obj: other)
else:
raise newException(NotImplementedError, "")
@ -216,7 +216,7 @@ proc stringify*(self: ptr Float): string =
proc isFalsey*(self: ptr Float): bool =
result = self.floatValue == 0.0
result = false
proc hash*(self: ptr Float): uint64 =
@ -243,7 +243,7 @@ proc stringify*(self: ptr Integer): string =
proc isFalsey*(self: ptr Integer): bool =
result = self.intValue == 0
result = false
proc eq*(self, other: ptr Integer): bool =
@ -259,66 +259,90 @@ proc hash*(self: ptr Integer): uint64 =
result = uint64 self.intValue
proc lt*(self: ptr Integer, other: ptr Obj): bool =
proc lt*(self: ptr Integer, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
result = self.intValue < cast[ptr Integer](other).intValue
if self.intValue < cast[ptr Integer](other).intValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Float:
result = (float self.intValue) < cast[ptr Float](other).floatValue
if (float self.intValue) < cast[ptr Float](other).floatValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = false
result = (result: true, obj: other)
else:
result = true
result = (result: false, obj: nil)
else:
raise newException(NotImplementedError, "")
proc lt*(self: ptr Float, other: ptr Obj): bool =
proc lt*(self: ptr Float, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
result = self.floatValue < (float cast[ptr Integer](other).intValue)
if self.floatValue < (float cast[ptr Integer](other).intValue):
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Float:
result = self.floatValue < cast[ptr Float](other).floatValue
if self.floatValue < cast[ptr Float](other).floatValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = false
result = (result: true, obj: other)
else:
result = true
result = (result: false, obj: nil)
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Integer, other: ptr Obj): bool =
proc gt*(self: ptr Integer, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
result = self.intValue > cast[ptr Integer](other).intValue
if self.intValue > cast[ptr Integer](other).intValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Float:
result = (float self.intValue) > cast[ptr Float](other).floatValue
if (float self.intValue) > cast[ptr Float](other).floatValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = true
result = (result: true, obj: other)
else:
result = false
result = (result: false, obj: nil)
else:
raise newException(NotImplementedError, "")
proc gt*(self: ptr Float, other: ptr Obj): bool =
proc gt*(self: ptr Float, other: ptr Obj): tuple[result: bool, obj: ptr Obj] =
case other.kind:
of ObjectType.Integer:
result = self.floatValue > (float cast[ptr Integer](other).intValue)
if self.floatValue > (float cast[ptr Integer](other).intValue):
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Float:
result = self.floatValue > cast[ptr Float](other).floatValue
if self.floatValue > cast[ptr Float](other).floatValue:
result = (result: true, obj: other)
else:
result = (result: false, obj: nil)
of ObjectType.Infinity:
let other = cast[ptr Infinity](other)
if other.isNegative:
result = true
result = (result: true, obj: other)
else:
result = false
result = (result: false, obj: nil)
else:
raise newException(NotImplementedError, "")

View File

@ -505,8 +505,13 @@ proc run(self: VM): InterpretResult =
# Binary less (<)
var right = self.pop()
var left = self.pop()
var comp: tuple[result: bool, obj: ptr Obj]
try:
self.push(self.getBoolean(left.lt(right)))
comp = left.lt(right)
if system.`==`(comp.obj, nil):
self.push(self.getBoolean(comp.result))
else:
self.push(comp.obj)
except NotImplementedError:
self.error(newTypeError(&"unsupported binary operator '<' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
@ -514,18 +519,55 @@ proc run(self: VM): InterpretResult =
# Binary greater (>)
var right = self.pop()
var left = self.pop()
var comp: tuple[result: bool, obj: ptr Obj]
try:
self.push(self.getBoolean(left.gt(right)))
comp = left.gt(right)
if system.`==`(comp.obj, nil):
self.push(self.getBoolean(comp.result))
else:
self.push(comp.obj)
except NotImplementedError:
self.error(newTypeError(&"unsupported binary operator '>' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.LessOrEqual:
var right = self.pop()
var left = self.pop()
var comp: tuple[result: bool, obj: ptr Obj]
try:
comp = left.lt(right)
if not comp.result and left == right:
comp.result = true
if system.`==`(comp.obj, nil):
self.push(self.getBoolean(comp.result))
else:
self.push(comp.obj)
except NotImplementedError:
self.error(newTypeError(&"unsupported binary operator '<' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.GreaterOrEqual:
var right = self.pop()
var left = self.pop()
var comp: tuple[result: bool, obj: ptr Obj]
try:
comp = left.gt(right)
if not comp.result and left == right:
comp.result = true
if system.`==`(comp.obj, nil):
self.push(self.getBoolean(comp.result))
else:
self.push(comp.obj)
except NotImplementedError:
self.error(newTypeError(&"unsupported binary operator '>' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
return RuntimeError
of OpCode.Is:
# Implements object identity (i.e. same pointer)
# This is implemented internally for obvious
# reasons and works on any pair of objects
# reasons and works on any pair of objects, which
# is why we call nim's system.== operator and NOT
# our custom one
var right = self.pop()
var left = self.pop()
self.push(self.getBoolean(left == right))
self.push(self.getBoolean(system.`==`(left, right)))
of OpCode.As:
# Implements type casting (TODO: Only allow classes)
var right = self.pop()
@ -629,6 +671,7 @@ proc run(self: VM): InterpretResult =
# Handles returning values from the callee to the caller
# and sets up the stack to proceed with execution
var retResult = self.pop()
# Pops the function's frame
discard self.frames.pop()
if self.frames.len() == 0:
discard self.pop()

View File

@ -9,17 +9,17 @@ 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(nil or 4);//stdout:4
print(0 or 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 false);//stdout:true
print(not 1);//stdout:false
print(not 1 and not 2);//stdout:false
print(not (1 and 0));//stdout:true
print(not (1 and false));//stdout:true
[end]
[end]

View File

@ -1,5 +1,4 @@
[Test: is]
[skip]
[source:mixed]
var x = 4;
var y = x;