Initial work on call resolution, added support for running files

This commit is contained in:
Mattia Giambirtone 2022-05-22 13:02:48 +02:00
parent 6d6ae3ee7a
commit 42ab1d4c6e
10 changed files with 352 additions and 56 deletions

BIN
main Executable file

Binary file not shown.

BIN
peon Executable file

Binary file not shown.

View File

@ -23,6 +23,7 @@ import algorithm
import parseutils
import strutils
import sequtils
import os
export ast
@ -43,9 +44,11 @@ type
node*: ASTNode
case kind*: TypeKind:
of Function:
args*: seq[tuple[name: IdentExpr, kind: Type]]
returnType*: Type
else:
discard
# This way we don't have recursive dependency issues
import meta/bytecode
export bytecode
@ -158,6 +161,8 @@ proc identifier(self: Compiler, node: IdentExpr)
proc varDecl(self: Compiler, node: VarDecl)
proc inferType(self: Compiler, node: LiteralExpr): Type
proc inferType(self: Compiler, node: Expression): Type
proc findByName(self: Compiler, name: string): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type): seq[Name]
## End of forward declarations
## Public getter for nicer error formatting
@ -190,7 +195,7 @@ proc done(self: Compiler): bool =
proc error(self: Compiler, message: string) {.raises: [CompileError].} =
## Raises a CompileError exception
raise newException(CompileError, message)
raise CompileError(msg: message, node: self.getCurrentNode(), file: self.file, module: self.currentModule)
proc step(self: Compiler): ASTNode =
@ -201,7 +206,7 @@ proc step(self: Compiler): ASTNode =
self.current += 1
proc emitByte(self: Compiler, byt: OpCode|uint8) =
proc emitByte(self: Compiler, byt: OpCode | uint8) =
## Emits a single byte, writing it to
## the current chunk being compiled
when DEBUG_TRACE_COMPILER:
@ -209,23 +214,8 @@ proc emitByte(self: Compiler, byt: OpCode|uint8) =
self.chunk.write(uint8 byt, self.peek().token.line)
proc emitBytes(self: Compiler, byt1: OpCode|uint8, byt2: OpCode|uint8) =
## Emits multiple bytes instead of a single one. This is useful
## to emit operators along with their operands or for multi-byte
## instructions that are longer than one byte
self.emitByte(uint8 byt1)
self.emitByte(uint8 byt2)
proc emitBytes(self: Compiler, bytarr: array[2, uint8]) =
## Handy helper method to write an array of 2 bytes into
## the current chunk, calling emitByte on each of its
## elements
self.emitBytes(bytarr[0], bytarr[1])
proc emitBytes(self: Compiler, bytarr: openarray[uint8]) =
## Handy helper method to write an array of 3 bytes into
proc emitBytes(self: Compiler, bytarr: openarray[OpCode | uint8]) =
## Handy helper method to write arbitrary bytes into
## the current chunk, calling emitByte on each of its
## elements
for b in bytarr:
@ -390,6 +380,30 @@ proc detectClosureVariable(self: Compiler, name: IdentExpr,
self.chunk.code[entry.codePos + 2] = idx[1]
self.chunk.code[entry.codePos + 3] = idx[2]
proc compareTypes(self: Compiler, a, b: Type): bool
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
## Compares two types without using information from
## AST nodes
if a == nil:
return b == nil
elif b == nil:
return a == nil
if a.kind != b.kind:
return false
case a.kind:
of Function:
if a.args.len() != b.args.len():
return false
elif not self.compareTypes(a.returnType, b.returnType):
return false
for (argA, argB) in zip(a.args, b.args):
if not self.compareTypes(argA.kind, argB.kind):
return false
return true
else:
discard
proc compareTypes(self: Compiler, a, b: Type): bool =
## Compares two type objects
@ -406,6 +420,8 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
Char, Byte, String, Nil, Nan, Bool, Inf:
return true
of Function:
if a.node == nil or b.node == nil:
return self.compareTypesWithNullNode(a, b)
let
a = FunDecl(a.node)
b = FunDecl(b.node)
@ -413,8 +429,7 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
return false
elif a.arguments.len() != b.arguments.len():
return false
elif not self.compareTypes(self.inferType(a.returnType),
self.inferType(b.returnType)):
elif not self.compareTypes(self.inferType(a.returnType), self.inferType(b.returnType)):
return false
for (argA, argB) in zip(a.arguments, b.arguments):
if argA.mutable != argB.mutable:
@ -423,8 +438,7 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
return false
elif argA.isPtr != argB.isPtr:
return false
elif not self.compareTypes(self.inferType(argA.valueType),
self.inferType(argB.valueType)):
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
return false
return true
else:
@ -702,7 +716,10 @@ proc unary(self: Compiler, node: UnaryExpr) =
## Compiles unary expressions such as decimal
## and bitwise negation
self.expression(node.a) # Pushes the operand onto the stack
let valueType = self.inferType(node.a)
let impl = self.findByType(node.token.lexeme, Type(kind: Function, returnType: valueType, node: nil))
if impl.len() == 0:
self.error(&"cannot find a suitable implementation for '{node.token.lexeme}'")
proc binary(self: Compiler, node: BinaryExpr) =
@ -772,10 +789,12 @@ proc declareName(self: Compiler, node: Declaration) =
owner: self.currentModule,
valueType: Type(kind: Function, node: node,
returnType: self.inferType(
node.returnType)),
node.returnType),
args: @[]),
codePos: self.chunk.code.len(),
name: node.name,
isLet: false))
let fn = self.names[^1]
for argument in node.arguments:
if self.names.high() > 16777215:
self.error("cannot declare more than 16777216 variables at a time")
@ -789,6 +808,7 @@ proc declareName(self: Compiler, node: Declaration) =
isLet: false))
self.names[^1].valueType = self.inferType(argument.valueType)
self.names[^1].valueType.node = argument.name
fn.valueType.args.add((node.name, self.names[^1].valueType))
self.emitByte(LoadVar)
self.emitBytes(self.names.high().toTriple())
else:
@ -1274,7 +1294,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
self.names = @[]
self.scopeDepth = 0
self.currentFunction = nil
self.currentModule = self.file
self.currentModule = self.file.extractFilename()
self.current = 0
while not self.done():
self.declaration(Declaration(self.step()))

View File

@ -19,7 +19,6 @@ import strutils
import parseutils
import strformat
import tables
import terminal
import meta/token
@ -203,7 +202,7 @@ proc step(self: Lexer, n: int = 1): string =
inc(self.current)
proc peek*(self: Lexer, distance: int = 0, length: int = 1): string =
proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
## Returns a stream of characters of
## at most length bytes from the source
## file, starting at the given distance,
@ -226,7 +225,7 @@ proc peek*(self: Lexer, distance: int = 0, length: int = 1): string =
proc error(self: Lexer, message: string) =
## Raises a lexing error with a formatted
## error message
raise newException(LexingError, message)
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek())
proc check(self: Lexer, s: string, distance: int = 0): bool =

View File

@ -11,12 +11,24 @@
# 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 token
import ast
type
## Nim exceptions for internal Peon failures
PeonException* = object of CatchableError
LexingError* = object of PeonException
ParseError* = object of PeonException
CompileError* = object of PeonException
SerializationError* = object of PeonException
PeonException* = ref object of CatchableError
LexingError* = ref object of PeonException
file*: string
lexeme*: string
line*: int
ParseError* = ref object of PeonException
file*: string
token*: Token
module*: string
CompileError* = ref object of PeonException
node*: ASTNode
file*: string
module*: string
SerializationError* = ref object of PeonException
file*: string

View File

@ -17,6 +17,7 @@
import strformat
import strutils
import tables
import os
import meta/token
import meta/ast
@ -152,6 +153,7 @@ proc getCurrentToken*(self: Parser): Token {.inline.} = (if self.getCurrent() >=
self.getCurrent() - 1 < 0: self.tokens[^1] else: self.tokens[self.current - 1])
proc getCurrentFunction*(self: Parser): Declaration {.inline.} = self.currentFunction
proc getFile*(self: Parser): string {.inline.} = self.file
proc getModule*(self: Parser): string {.inline.} = self.getFile().extractFilename()
# Handy templates to make our life easier, thanks nim!
template endOfFile: Token = Token(kind: EndOfFile, lexeme: "", line: -1)
@ -192,7 +194,7 @@ proc step(self: Parser, n: int = 1): Token =
proc error(self: Parser, message: string) {.raises: [ParseError].} =
## Raises a ParseError exception
raise newException(ParseError, message)
raise ParseError(msg: message, token: self.getCurrentToken(), file: self.file, module: self.getModule())
# Why do we allow strings or enum members of TokenType? Well, it's simple:

View File

@ -2,6 +2,7 @@
import strformat
import strutils
import terminal
import os
# Thanks art <3
import jale/editor as ed
import jale/templates
@ -18,7 +19,6 @@ import frontend/compiler as c
import backend/vm as v
import util/serializer as s
# Forward declarations
proc fillSymbolTable(tokenizer: Lexer)
proc getLineEditor: LineEditor
@ -38,8 +38,8 @@ when debugCompiler:
import util/debugger
when isMainModule:
setControlCHook(proc () {.noconv.} = quit(0))
proc repl =
styledEcho fgMagenta, "Welcome into the peon REPL!"
var
keep = true
tokens: seq[Token] = @[]
@ -121,50 +121,116 @@ when isMainModule:
when debugRuntime:
styledEcho fgCyan, "\n\nExecution step: "
vm.run(serialized.chunk)
# TODO: The code for error reporting completely
# breaks down with multiline input, fix it
except LexingError:
let lineNo = tokenizer.getLine()
let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{tokenizer.getFile()}'", fgRed, ", module ",
fgYellow, &"'{tokenizer.getFile()}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{tokenizer.peek()}'",
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
let lexeme = parser.getCurrentToken().lexeme
let lineNo = parser.getCurrentToken().line
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo)
let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{parser.getFile()}'", fgRed, ", module ",
fgYellow, &"'{parser.getFile()}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError:
let lexeme = compiler.getCurrentNode().token.lexeme
let lineNo = compiler.getCurrentNode().token.line
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fn = compiler.getCurrentFunction()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{compiler.getFile()}'", fgRed, ", module ",
fgYellow, &"'{compiler.getModule()}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except SerializationError:
stderr.styledWriteLine(fgRed, getCurrentExceptionMsg())
let exc = SerializationError(getCurrentException())
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
quit(0)
proc runFile(f: string) =
var
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
vm = newPeonVM()
tokenizer.fillSymbolTable()
try:
vm.run(compiler.compile(parser.parse(tokenizer.lex(readFile(f), f), f), f))
except LexingError:
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo)
let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError:
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fn = compiler.getCurrentFunction()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except SerializationError:
let exc = SerializationError(getCurrentException())
stderr.styledWriteLine(fgRed, "A fatal error occurred while (de-)serializing", fgYellow, &"'{exc.file}'", fgGreen, ": ", getCurrentExceptionMsg())
except IOError:
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {getCurrentExceptionMsg()}")
except OSError:
stderr.styledWriteLine("An error occurred while trying to read ", fgYellow, &"'{f}'", fgRed, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
when isMainModule:
setControlCHook(proc () {.noconv.} = quit(0))
let args = commandLineParams()
if args.len() == 0:
repl()
else:
runFile(args[0])
proc fillSymbolTable(tokenizer: Lexer) =
## Initializes the Lexer's symbol

View File

@ -0,0 +1,193 @@
## Builtin arithmetic operators for Peon
operator `+`(a, b: int): int {
#pragma[magic: AddInt64, pure]
return;
}
operator `+`(a, b: uint): uint {
#pragma[magic: AddUInt64, pure]
return;
}
operator `+`(a, b: int32): int32 {
#pragma[magic: AddInt32, pure]
return;
}
operator `+`(a, b: uint32): uint32 {
#pragma[magic: AddUInt32, pure]
return;
}
operator `+`(a, b: int16): int16 {
#pragma[magic: AddInt16, pure]
return;
}
operator `+`(a, b: uint16): uint16 {
#pragma[magic: AddUInt16, pure]
return;
}
operator `+`(a, b: int8): int8 {
#pragma[magic: AddInt8, pure]
return;
}
operator `+`(a, b: uint8): uint8 {
#pragma[magic: AddUInt8, pure]
return;
}
operator `-`(a, b: int): int {
#pragma[magic: SubInt64, pure]
return;
}
operator `-`(a, b: uint): uint {
#pragma[magic: SubUInt64, pure]
return;
}
operator `-`(a, b: int32): int32 {
#pragma[magic: SubInt32, pure]
return;
}
operator `-`(a, b: uint32): uint32 {
#pragma[magic: SubUInt32, pure]
return;
}
operator `-`(a, b: int16): int16 {
#pragma[magic: SubInt16, pure]
return;
}
operator `-`(a, b: uint16): uint16 {
#pragma[magic: SubUInt16, pure]
return;
}
operator `-`(a, b: int8): int8 {
#pragma[magic: SubInt8, pure]
return;
}
operator `-`(a, b: uint8): uint8 {
#pragma[magic: SubUInt8, pure]
return;
}
operator `*`(a, b: int): int {
#pragma[magic: MulInt64, pure]
return;
}
operator `*`(a, b: uint): uint {
#pragma[magic: MulUInt64, pure]
return;
}
operator `*`(a, b: int32): int32 {
#pragma[magic: MulInt32, pure]
return;
}
operator `*`(a, b: uint32): uint32 {
#pragma[magic: MulUInt32, pure]
return;
}
operator `*`(a, b: int16): int16 {
#pragma[magic: MulInt16, pure]
return;
}
operator `*`(a, b: uint16): uint16 {
#pragma[magic: MulUInt16, pure]
return;
}
operator `*`(a, b: int8): int8 {
#pragma[magic: MulInt8, pure]
return;
}
operator `*`(a, b: uint8): uint8 {
#pragma[magic: MulUInt8, pure]
return;
}
operator `/`(a, b: int): int {
#pragma[magic: DivInt64, pure]
return;
}
operator `/`(a, b: uint): uint {
#pragma[magic: DivUInt64, pure]
return;
}
operator `/`(a, b: int32): int32 {
#pragma[magic: DivInt32, pure]
return;
}
operator `/`(a, b: uint32): uint32 {
#pragma[magic: DivUInt32, pure]
return;
}
operator `/`(a, b: int16): int16 {
#pragma[magic: DivInt16, pure]
return;
}
operator `/`(a, b: uint16): uint16 {
#pragma[magic: DivUInt16, pure]
return;
}
operator `/`(a, b: int8): int8 {
#pragma[magic: DivInt8, pure]
return;
}
operator `/`(a, b: uint8): uint8 {
#pragma[magic: DivUInt8, pure]
return;
}

5
src/tests.pn Normal file
View File

@ -0,0 +1,5 @@
operator `+`(a: int): int {
return a;
}
+1;

View File

@ -49,8 +49,7 @@ proc `$`*(self: Serialized): string =
proc error(self: Serializer, message: string) =
## Raises a formatted SerializationError exception
raise newException(SerializationError,
&"A fatal error occurred while (de)serializing '{self.filename}' -> {message}")
raise SerializationError(msg: message, file: self.filename)
proc newSerializer*(self: Serializer = nil): Serializer =