From 79af16ccc9f6194872cf98d6d25c8150e94eff03 Mon Sep 17 00:00:00 2001 From: Nocturn9x Date: Thu, 7 Apr 2022 12:15:34 +0200 Subject: [PATCH] Parser support for forward declarations --- src/frontend/compiler.nim | 10 +--------- src/frontend/parser.nim | 31 ++++++++++++++++++++++--------- src/test.nim | 30 ++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 139c40a..57c9495 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -1006,12 +1006,6 @@ proc funDecl(self: Compiler, node: FunDecl) = self.currentFunction = function -proc classDecl(self: Compiler, node: ClassDecl) = - ## Compiles class declarations - self.declareName(node.name) - self.emitByte(MakeClass) - self.blockStmt(BlockStmt(node.body)) - proc declaration(self: Compiler, node: ASTNode) = ## Compiles all declarations @@ -1020,8 +1014,6 @@ proc declaration(self: Compiler, node: ASTNode) = self.varDecl(VarDecl(node)) of NodeKind.funDecl: self.funDecl(FunDecl(node)) - of NodeKind.classDecl: - self.classDecl(ClassDecl(node)) else: self.statement(node) @@ -1045,4 +1037,4 @@ proc compile*(self: Compiler, ast: seq[ASTNode], file: string): Chunk = self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope result = self.chunk if self.ast.len() > 0 and self.scopeDepth != -1: - self.error(&"internal error: invalid scopeDepth state (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?") + self.error(&"invalid state: invalid scopeDepth value (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?") diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 0552577..6299452 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -77,8 +77,12 @@ proc newParser*(): Parser = result.scopeDepth = 0 # Public getters for improved error formatting -proc getCurrent*(self: Parser): int = self.current -proc getCurrentToken*(self: Parser): Token = (if self.getCurrent() >= self.tokens.len(): self.tokens[^1] else: self.tokens[self.current - 1]) +proc getCurrent*(self: Parser): int {.inline.} = self.current +proc getCurrentToken*(self: Parser): Token = + if self.getCurrent() >= self.tokens.high() or self.getCurrent() - 1 < 0: + return self.tokens[^1] + else: + return self.tokens[self.current - 1] # Handy templates to make our life easier, thanks nim! template endOfFile: Token = Token(kind: EndOfFile, lexeme: "", line: -1) @@ -190,7 +194,8 @@ proc declaration(self: Parser): ASTNode proc primary(self: Parser): ASTNode = ## Parses primary expressions such ## as integer literals and keywords - ## that map to builtin types (true, false, etc) + ## that map to builtin types (true, + ## false, nil, etc.) case self.peek().kind: of True: result = newTrueExpr(self.step()) @@ -326,7 +331,7 @@ proc makeCall(self: Parser, callee: ASTNode): ASTNode = if not self.check(RightParen): while true: if argCount >= 255: - self.error("cannot store more than 255 arguments") + self.error("call can not have more than 255 arguments") break argument = self.expression() if argument.kind == assignExpr: @@ -337,7 +342,7 @@ proc makeCall(self: Parser, callee: ASTNode): ASTNode = elif arguments.keyword.len() == 0: arguments.positionals.add(argument) else: - self.error("positional argument(s) cannot follow keyword argument(s) in call") + self.error("positional argument cannot follow keyword argument in call") if not self.match(Comma): break argCount += 1 @@ -879,7 +884,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL return result elif isLambda: self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator=isGenerator, isAsync=isAsync, token=tok) - else: + elif not isOperator: self.error("funDecl: invalid state") if self.match(Colon): # A function without an explicit @@ -913,11 +918,17 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL # Function's return type self.expect(Identifier, "expecting return type after ':'") returnType = newIdentExpr(self.peek(-1)) - self.expect(LeftBrace) if self.currentFunction.kind == funDecl: if not self.match(Semicolon): - # Forward declaration! + # If we don't find a semicolon, + # it's not a forward declaration + self.expect(LeftBrace) FunDecl(self.currentFunction).body = self.blockStmt() + else: + # This is a forward declaration so we explicitly + # nullify the function's body to tell the compiler + # to look for it elsewhere in the file later + FunDecl(self.currentFunction).body = nil FunDecl(self.currentFunction).arguments = arguments FunDecl(self.currentFunction).returnType = returnType else: @@ -934,6 +945,8 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL self.error("cannot declare argument-less operator") elif arguments.len() > 2: self.error("cannot declare operator with more than 2 arguments") + elif FunDecl(result).returnType == nil: + self.error("operator cannot have void return type") self.currentFunction = enclosingFunction @@ -1047,7 +1060,7 @@ proc parse*(self: Parser, tokens: seq[Token], file: string): seq[ASTNode] = # ending the input with an EOF token if token.kind == Operator: if i == self.tokens.high(): - self.error("invalid state: found malformed tokenizer input while looking for operators") + self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)") elif self.tokens[i + 1].kind != Identifier: # The input is probably malformed, # we'll catch this error later diff --git a/src/test.nim b/src/test.nim index e53302d..56b9ec4 100644 --- a/src/test.nim +++ b/src/test.nim @@ -21,10 +21,15 @@ proc fillSymbolTable(tokenizer: Lexer) proc getLineEditor: LineEditor +const debugLexer = false +const debugParser = true + + when isMainModule: setControlCHook(proc () {.noconv.} = quit(0)) var keep = true var tokens: seq[Token] = @[] + var tree: seq[ASTNode] = @[] var tokenizer = newLexer() var parser = newParser() let editor = getLineEditor() @@ -42,18 +47,23 @@ when isMainModule: if input.len() > 0: # Currently the parser doesn't handle these tokens well tokens = filter(tokenizer.lex(input, ""), proc (x: Token): bool = x.kind notin {TokenType.Whitespace, Tab}) - echo "Tokenization step:" - for i, token in tokens: - if i == tokens.high(): - # Who cares about EOF? - break - echo "\t", token - echo "" - echo "Parsing step:" - for node in parser.parse(tokens, ""): - echo "\t", node + when debugLexer: + echo "Tokenization step:" + for i, token in tokens: + if i == tokens.high(): + # Who cares about EOF? + break + echo "\t", token + echo "" + tree = parser.parse(tokens, "") + when debugParser: + echo "Parsing step:" + for node in tree: + echo "\t", node except IOError: break + # 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)