diff --git a/main b/main new file mode 100755 index 0000000..ad60d1a Binary files /dev/null and b/main differ diff --git a/peon b/peon new file mode 100755 index 0000000..04b46ed Binary files /dev/null and b/peon differ diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 426657e..7da4d32 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -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())) diff --git a/src/frontend/lexer.nim b/src/frontend/lexer.nim index fce99cd..04c2f80 100644 --- a/src/frontend/lexer.nim +++ b/src/frontend/lexer.nim @@ -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 = diff --git a/src/frontend/meta/errors.nim b/src/frontend/meta/errors.nim index 3345574..3b6d57c 100644 --- a/src/frontend/meta/errors.nim +++ b/src/frontend/meta/errors.nim @@ -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 diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 0606da5..2f2a193 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -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: diff --git a/src/main.nim b/src/main.nim index 7722b00..e35fb2e 100644 --- a/src/main.nim +++ b/src/main.nim @@ -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 diff --git a/src/peon/stdlib/arithmetics.pn b/src/peon/stdlib/arithmetics.pn new file mode 100644 index 0000000..63768f1 --- /dev/null +++ b/src/peon/stdlib/arithmetics.pn @@ -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; +} \ No newline at end of file diff --git a/src/tests.pn b/src/tests.pn new file mode 100644 index 0000000..b994a99 --- /dev/null +++ b/src/tests.pn @@ -0,0 +1,5 @@ +operator `+`(a: int): int { + return a; +} + ++1; diff --git a/src/util/serializer.nim b/src/util/serializer.nim index 40daa76..b8cd10a 100644 --- a/src/util/serializer.nim +++ b/src/util/serializer.nim @@ -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 =