From b40275b52fe3fc7ab78b193800dd11197bfc59ff Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Sat, 16 Jul 2022 13:04:00 +0200 Subject: [PATCH] Added Compiler.check(), made type constraints mandatory in generics, reverted '\n' being converted to a semicolon, minor refactoring --- src/frontend/compiler.nim | 52 ++++++++++++++++++++++----------------- src/frontend/lexer.nim | 5 ++-- src/frontend/parser.nim | 34 ++++++++++++------------- src/main.nim | 4 +-- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index a004cc6..56b03a1 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -773,6 +773,22 @@ proc matchImpl(self: Compiler, name: string, kind: Type): Name = return impl[0] +proc check(self: Compiler, term: Expression, kind: Type) = + ## Checks the type of term against a known type. + ## Raises an error if appropriate and returns + ## otherwise + let k = self.inferType(term) + if k.isNil(): + if term.kind == identExpr: + self.error(&"reference to undeclared name '{term.token.lexeme}'") + elif term.kind == callExpr and CallExpr(term).callee.kind == identExpr: + self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'") + self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type") + elif not self.compareTypes(k, kind): + self.error(&"expecting value of type '{self.typeToStr(k)}', got '{self.typeToStr(k)}' instead") + + + proc emitFunction(self: Compiler, name: Name) = ## Wrapper to emit LoadFunction instructions if name.isFunDecl: @@ -1271,13 +1287,8 @@ proc blockStmt(self: Compiler, node: BlockStmt) = proc ifStmt(self: Compiler, node: IfStmt) = ## Compiles if/else statements for conditional ## execution of code - var cond = self.inferType(node.condition) + self.check(node.condition, Type(kind: Bool)) self.expression(node.condition) - if not self.compareTypes(cond, Type(kind: Bool)): - if cond.isNil(): - self.error(&"expecting value of type 'bool', but expression has no type") - else: - self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead") let jump = self.emitJump(JumpIfFalsePop) self.statement(node.thenBranch) let jump2 = self.emitJump(JumpForwards) @@ -1290,7 +1301,7 @@ proc ifStmt(self: Compiler, node: IfStmt) = proc emitLoop(self: Compiler, begin: int) = ## Emits a JumpBackwards instruction with the correct ## jump offset - var offset = self.chunk.code.len() - begin + 4 + let offset = self.chunk.code.len() - begin + 4 if offset > 16777215: self.error("cannot jump more than 16777215 bytecode instructions") self.emitByte(JumpBackwards) @@ -1300,15 +1311,10 @@ proc emitLoop(self: Compiler, begin: int) = proc whileStmt(self: Compiler, node: WhileStmt) = ## Compiles C-style while loops and ## desugared C-style for loops - var cond = self.inferType(node.condition) + self.check(node.condition, Type(kind: Bool)) self.expression(node.condition) - if not self.compareTypes(cond, Type(kind: Bool)): - if cond.isNil(): - self.error(&"expecting value of type 'bool', but expression has no type") - else: - self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead") let start = self.chunk.code.len() - var jump = self.emitJump(JumpIfFalsePop) + let jump = self.emitJump(JumpIfFalsePop) self.statement(node.body) self.patchJump(jump) self.emitLoop(start) @@ -1329,7 +1335,7 @@ proc callExpr(self: Compiler, node: CallExpr) = kind = self.inferType(argument) if kind.isNil(): if argument.kind == identExpr: - self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'") + self.error(&"reference to undeclared name '{IdentExpr(argument).name.lexeme}'") self.error(&"cannot infer the type of argument {i + 1} in function call") args.add(("", kind)) argExpr.add(argument) @@ -1459,7 +1465,7 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = if actual.isNil() and not expected.isNil(): if not node.value.isNil(): if node.value.kind == identExpr: - self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'") + self.error(&"reference to undeclared name '{node.value.token.lexeme}'") elif node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr: self.error(&"call to undeclared function '{CallExpr(node.value).callee.token.lexeme}'") self.error(&"expected return value of type '{self.typeToStr(expected)}', but expression has no type") @@ -1478,12 +1484,13 @@ proc returnStmt(self: Compiler, node: ReturnStmt) = self.emitByte(0) +# TODO: Implement this as a custom operator proc yieldStmt(self: Compiler, node: YieldStmt) = ## Compiles yield statements self.expression(node.expression) self.emitByte(OpCode.Yield) - +# TODO: Implement this as a custom operator proc raiseStmt(self: Compiler, node: RaiseStmt) = ## Compiles raise statements self.expression(node.exception) @@ -1551,11 +1558,10 @@ proc statement(self: Compiler, node: Statement) = # The expression has no type, so we don't have to # pop anything discard + elif self.replMode: + self.emitByte(PopRepl) else: - if self.replMode: - self.emitByte(PopRepl) - else: - self.emitByte(Pop) + self.emitByte(Pop) of NodeKind.ifStmt: self.ifStmt(IfStmt(node)) of NodeKind.assertStmt: @@ -1572,7 +1578,7 @@ proc statement(self: Compiler, node: Statement) = self.importStmt(ImportStmt(node)) of NodeKind.whileStmt: # Note: Our parser already desugars - # for loops to while loops! + # for loops to while loops let loop = self.currentLoop self.currentLoop = Loop(start: self.chunk.code.len(), depth: self.scopeDepth, breakPos: @[]) @@ -1604,7 +1610,7 @@ proc varDecl(self: Compiler, node: VarDecl) = var name = node.value.token.lexeme if node.value.kind == callExpr: name = CallExpr(node.value).callee.token.lexeme - self.error(&"reference to undeclared identifier '{name}'") + self.error(&"reference to undeclared name '{name}'") self.error(&"'{node.name.token.lexeme}' has no type") elif not expected.isNil() and expected.mutable: # I mean, variables *are* already mutable (some of them anyway) self.error(&"invalid type '{self.typeToStr(expected)}' for var") diff --git a/src/frontend/lexer.nim b/src/frontend/lexer.nim index 6995980..2201d36 100644 --- a/src/frontend/lexer.nim +++ b/src/frontend/lexer.nim @@ -565,8 +565,9 @@ proc next(self: Lexer) = elif self.match("\n"): # New line self.incLine() - if not self.getToken("\n").isNil(): - self.createToken(Semicolon) + # TODO: Broken + #[if not self.getToken("\n").isNil(): + self.createToken(Semicolon)]# elif self.match("`"): # Stropped token self.parseBackticks() diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 0b7d903..5ebac51 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -155,11 +155,11 @@ proc getCurrentFunction*(self: Parser): Declaration {.inline.} = self.currentFun 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) template endOfLine(msg: string) = self.expect(Semicolon, msg) + proc peek(self: Parser, distance: int = 0): Token = ## Peeks at the token at the given distance. ## If the distance is out of bounds, an EOF @@ -251,8 +251,7 @@ proc match[T: TokenType or string](self: Parser, kind: openarray[T]): bool = result = false -proc expect[T: TokenType or string](self: Parser, kind: T, - message: string = "") = +proc expect[T: TokenType or string](self: Parser, kind: T, message: string = "") = ## Behaves like self.match(), except that ## when a token doesn't match, an error ## is raised. If no error message is @@ -264,8 +263,7 @@ proc expect[T: TokenType or string](self: Parser, kind: T, self.error(message) -proc expect[T: TokenType or string](self: Parser, kind: openarray[T], - message: string = "") = +proc expect[T: TokenType or string](self: Parser, kind: openarray[T], message: string = "") = ## Behaves like self.expect(), except that ## an error is raised only if none of the ## given token kinds matches @@ -273,7 +271,7 @@ proc expect[T: TokenType or string](self: Parser, kind: openarray[T], if self.match(kind): return if message.len() == 0: - self.error(&"""expecting any of the following tokens: {kinds.join(", ")}, but got {self.peek().kind} instead""") + self.error(&"""expecting any of the following tokens: {kind.join(", ")}, but got {self.peek().kind} instead""") # Forward declarations @@ -526,7 +524,7 @@ proc assertStmt(self: Parser): Statement = ## fed into them is falsey let tok = self.peek(-1) var expression = self.expression() - endOfLine("missing semicolon after assert statement") + endOfLine("missing statement terminator after 'assert'") result = newAssertStmt(expression, tok) @@ -561,7 +559,7 @@ proc breakStmt(self: Parser): Statement = let tok = self.peek(-1) if self.currentLoop != Loop: self.error("'break' cannot be used outside loops") - endOfLine("missing semicolon after break statement") + endOfLine("missing statement terminator after 'break'") result = newBreakStmt(tok) @@ -571,7 +569,7 @@ proc deferStmt(self: Parser): Statement = if self.currentFunction.isNil(): self.error("'defer' cannot be used outside functions") result = newDeferStmt(self.expression(), tok) - endOfLine("missing semicolon after defer statement") + endOfLine("missing statement terminator after 'defer'") proc continueStmt(self: Parser): Statement = @@ -579,7 +577,7 @@ proc continueStmt(self: Parser): Statement = let tok = self.peek(-1) if self.currentLoop != Loop: self.error("'continue' cannot be used outside loops") - endOfLine("missing semicolon after continue statement") + endOfLine("missing statement terminator after 'continue'") result = newContinueStmt(tok) @@ -594,7 +592,7 @@ proc returnStmt(self: Parser): Statement = # we need to check if there's an actual value # to return or not value = self.expression() - endOfLine("missing semicolon after return statement") + endOfLine("missing statement terminator after 'return'") result = newReturnStmt(value, tok) case self.currentFunction.kind: of NodeKind.funDecl: @@ -614,7 +612,7 @@ proc yieldStmt(self: Parser): Statement = result = newYieldStmt(self.expression(), tok) else: result = newYieldStmt(newNilExpr(Token(lexeme: "nil")), tok) - endOfLine("missing semicolon after yield statement") + endOfLine("missing statement terminator after 'yield'") proc awaitStmt(self: Parser): Statement = @@ -625,7 +623,7 @@ proc awaitStmt(self: Parser): Statement = if self.currentFunction.token.kind != Coroutine: self.error("'await' can only be used inside coroutines") result = newAwaitStmt(self.expression(), tok) - endOfLine("missing semicolon after await statement") + endOfLine("missing statement terminator after 'await'") proc raiseStmt(self: Parser): Statement = @@ -636,7 +634,7 @@ proc raiseStmt(self: Parser): Statement = # Raise can be used on its own, in which # case it re-raises the last active exception exception = self.expression() - endOfLine("missing semicolon after raise statement") + endOfLine("missing statement terminator after 'raise'") result = newRaiseStmt(exception, tok) @@ -666,7 +664,7 @@ proc importStmt(self: Parser, fromStmt: bool = false): Statement = # TODO: New AST node self.expect(Identifier, "expecting module name(s) after import statement") result = newImportStmt(newIdentExpr(self.peek(-1), self.scopeDepth), tok) - endOfLine("missing semicolon after import statement") + endOfLine("missing statement terminator after 'import'") @@ -932,8 +930,8 @@ proc parseGenerics(self: Parser, decl: Declaration) = while not self.check(RightBracket) and not self.done(): self.expect(Identifier, "expecting generic type name") gen.name = newIdentExpr(self.peek(-1), self.scopeDepth) - if self.match(":"): - gen.cond = self.expression() + self.expect(":", "expecting type constraint after generic name") + gen.cond = self.expression() decl.generics.add(gen) if not self.match(Comma): break @@ -1054,7 +1052,7 @@ proc expressionStatement(self: Parser): Statement = ## Parses expression statements, which ## are expressions followed by a semicolon var expression = self.expression() - endOfLine("missing semicolon after expression") + endOfLine("missing expression terminator") result = Statement(newExprStmt(expression, expression.token)) diff --git a/src/main.nim b/src/main.nim index 8611373..b843905 100644 --- a/src/main.nim +++ b/src/main.nim @@ -380,8 +380,8 @@ proc fillSymbolTable(tokenizer: Lexer) = tokenizer.symbols.addSymbol("]", RightBracket) tokenizer.symbols.addSymbol(".", Dot) tokenizer.symbols.addSymbol(",", Comma) - # tokenizer.symbols.addSymbol(";", Semicolon) - tokenizer.symbols.addSymbol("\n", Semicolon) + tokenizer.symbols.addSymbol(";", Semicolon) + # tokenizer.symbols.addSymbol("\n", Semicolon) # TODO: Broken # Keywords tokenizer.symbols.addKeyword("type", TokenType.Type) tokenizer.symbols.addKeyword("enum", Enum)