From 1e139194b871e119c8eac7fb5b666aae9221f056 Mon Sep 17 00:00:00 2001 From: prod2 <95874442+prod2@users.noreply.github.com> Date: Sat, 3 Dec 2022 09:32:41 +0100 Subject: [PATCH] block expressions, improved error reporting --- src/ndspkg/compv2/parser.nim | 110 ++++++++++++++++++++++------------- src/ndspkg/config.nim | 2 +- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/ndspkg/compv2/parser.nim b/src/ndspkg/compv2/parser.nim index b2b7d65..e24795e 100644 --- a/src/ndspkg/compv2/parser.nim +++ b/src/ndspkg/compv2/parser.nim @@ -93,10 +93,15 @@ proc match(parser: Parser, tokenTypes: set[TokenType]): bool = else: false -proc consume(parser: Parser, tokenType: TokenType | set[TokenType], msg: string) = - if not parser.match(tokenType): +proc consume(parser: Parser, tokenType: TokenType | set[TokenType], msg: string): bool = + # return val is useful for avoiding infinite loops + # if there was an error, might as well exit loops that try to consume more + if parser.match(tokenType): + true + else: parser.errorAtCurrent(msg) - parser.advance() # stop infinite loops + false + proc peek(parser: Parser): Token = parser.current @@ -107,9 +112,9 @@ proc peekMatch(parser: Parser, tokenType: TokenType): bool = proc synchronize(parser: Parser) = parser.panicMode = false while parser.current.tokenType != tkEof: - if parser.previous.get().tokenType in {tkSemicolon, tkRightBrace}: + if parser.previous.get().tokenType in {tkSemicolon}: return - if parser.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile}: + if parser.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile, tkRightBrace}: return parser.advance() @@ -118,6 +123,7 @@ proc isAtEnd(parser: Parser): bool = # EXPRESSIONS proc expression(parser: Parser): Node +proc statement(parser: Parser, inBlock: bool = false): Node # expressions, but not assignments proc exprNonAssign(parser: Parser): Node @@ -126,10 +132,10 @@ proc parseList(parser: Parser): Node = while not parser.isAtEnd() and not parser.peekMatch(tkRightBracket): result.elems.add(parser.expression()) - if parser.peek().tokenType != tkRightBracket: - parser.consume(tkComma, "',' expected after list elements.") + if parser.peek().tokenType != tkRightBracket and not parser.consume(tkComma, "',' expected after list elements."): + break - parser.consume(tkRightBracket, "']' expected after list declaration.") + discard parser.consume(tkRightBracket, "']' expected after list declaration.") proc parseTable(parser: Parser): Node = result = Node(kind: nkTable, keys: @[], values: @[]) @@ -138,19 +144,19 @@ proc parseTable(parser: Parser): Node = # [key] = syntax if parser.match(tkLeftBracket): result.keys.add(parser.expression()) - parser.consume(tkRightBracket, "']' expected after table key.") + discard parser.consume(tkRightBracket, "']' expected after table key.") # key = syntax elif parser.match(tkIdentifier): result.keys.add(Node(kind: nkConst, constant: parser.previous.get().text.fromNimString())) else: parser.errorAtCurrent("Key expected (have you forgotten to put the key in brackets?).") - parser.consume(tkEqual, "'=' expected after key.") + discard parser.consume(tkEqual, "'=' expected after key.") result.values.add(parser.exprNonAssign()) - if parser.peek().tokenType != tkRightBrace: - parser.consume(tkComma, "',' expected after table key value pair.") + if parser.peek().tokenType != tkRightBrace and not parser.consume(tkComma, "',' expected after table key value pair."): + break - parser.consume(tkRightBrace, "'}' expected after table declaration.") + discard parser.consume(tkRightBrace, "'}' expected after table declaration.") proc parseProcDeclaration(parser: Parser): Node = # returns a nkProc, assumes that the left paren @@ -160,16 +166,38 @@ proc parseProcDeclaration(parser: Parser): Node = var params: seq[string] = @[] while not parser.isAtEnd() and not parser.peekMatch(tkRightParen): - parser.consume(tkIdentifier, "Parameter name expected.") + if not parser.consume(tkIdentifier, "Parameter name expected."): + break params.add(parser.previous.get().text) - if not parser.isAtEnd() and not parser.peekMatch(tkRightParen): - parser.consume(tkComma, "',' expected between parameters.") - parser.consume(tkRightParen, "')' expected after parameter list.") + if not parser.isAtEnd() and not parser.peekMatch(tkRightParen) and not parser.consume(tkComma, "',' expected between parameters."): + break + discard parser.consume(tkRightParen, "')' expected after parameter list.") let body = parser.expression() result = Node(kind: nkProc, parameters: params, procBody: body) +proc parseBlock(parser: Parser): Node = + result = Node(kind: nkBlockExpr, children: @[], labels: @[]) + + while parser.match(tkLabel): + result.labels.add(parser.previous.get().text[1..^1]) + + while not parser.isAtEnd(): + if parser.peekMatch(tkRightBrace): + break + let child = parser.statement(true) + if child == nil: + # there was an error, but it should have already been reported + # we will be in panic mode anyways + parser.errorAtCurrent("Internal error: Block got empty expression.") + return + result.children.add(child) + if child.kind == nkExpr: + break + + discard parser.consume(tkRightBrace, "'}' expected after block expression.") + proc primary(parser: Parser): Node = if parser.match(tkFalse): return Node(kind: nkConst, constant: ndFalse) @@ -183,8 +211,10 @@ proc primary(parser: Parser): Node = return Node(kind: nkConst, constant: fromNimString(parser.previous.get().text[1..^2])) if parser.match(tkLeftParen): let grouped = parser.expression() - parser.consume(tkRightParen, "Expect ')' after expression.") + discard parser.consume(tkRightParen, "Expect ')' after expression.") return Node(kind: nkExpr, expression: grouped) + if parser.match(tkLeftBrace): + return parser.parseBlock() if parser.match(tkStartList): return parser.parseList() if parser.match(tkStartTable): @@ -196,7 +226,7 @@ proc primary(parser: Parser): Node = parser.hold = nil return result if parser.match(tkProc): - parser.consume(tkLeftParen, "'(' expected after 'proc'.") + discard parser.consume(tkLeftParen, "'(' expected after 'proc'.") return parser.parseProcDeclaration() parser.errorAtCurrent("Primary expected, but something else found.") @@ -206,10 +236,10 @@ proc parseArgList(parser: Parser): seq[Node] = var args: seq[Node] = @[] while not parser.isAtEnd() and not parser.peekMatch(tkRightParen): let arg = parser.expression() - if not parser.isAtEnd() and not parser.peekMatch(tkRightParen): - parser.consume(tkComma, "',' expected between arguments.") + if not parser.isAtEnd() and not parser.peekMatch(tkRightParen) and not parser.consume(tkComma, "',' expected between arguments."): + break args.add(arg) - parser.consume(tkRightParen, "')' expected after argument list.") + discard parser.consume(tkRightParen, "')' expected after argument list.") return args @@ -220,7 +250,8 @@ proc parseIndex(parser: Parser): Node = # NOTE: :index is counted as a single identifier, so two identifiers after eachother will be handled here if parser.previous.get().tokenType == tkLeftBracket: let index = parser.expression() - parser.consume(tkRightBracket, "']' after index.") + if not parser.consume(tkRightBracket, "']' after index."): + break result = Node(kind: nkGetIndex, gCollection: result, gIndex: index) elif parser.previous.get().tokenType == tkIdentifier: let identText = parser.previous.get().text @@ -237,7 +268,8 @@ proc parseIndex(parser: Parser): Node = result = Node(kind: nkColonCall, arguments: args, function: funct) else: # dot - parser.consume(tkIdentifier, "Identifier expected after '.' index operator.") + if not parser.consume(tkIdentifier, "Identifier expected after '.' index operator."): + break result = Node(kind: nkGetIndex, gCollection: result, gIndex: Node(kind: nkConst, constant: parser.previous.get().text.fromNimString())) proc parseCall(parser: Parser): Node = @@ -248,9 +280,9 @@ proc parseCall(parser: Parser): Node = proc parseIf(parser: Parser): Node = - parser.consume(tkLeftParen, "'(' expected after 'if'.") + discard parser.consume(tkLeftParen, "'(' expected after 'if'.") let cond = parser.expression() - parser.consume(tkRightParen, "')' expected after condition.") + discard parser.consume(tkRightParen, "')' expected after condition.") let body = parser.expression() result = Node(kind: nkIf, ifCondition: cond, ifBody: body) @@ -258,9 +290,9 @@ proc parseIf(parser: Parser): Node = result.elseBody = parser.expression() proc parseWhile(parser:Parser): Node = - parser.consume(tkLeftParen, "'(' expected after 'while'.") + discard parser.consume(tkLeftParen, "'(' expected after 'while'.") let cond = parser.expression() - parser.consume(tkRightParen, "')' expected after condition.") + discard parser.consume(tkRightParen, "')' expected after condition.") let body = parser.expression() result = Node(kind: nkWhile, whileCondition: cond, whileBody: body) @@ -404,18 +436,18 @@ proc expression(parser: Parser): Node = parser.parseAmpersand() # STATEMENTS - -proc statement(parser: Parser): Node - -proc exprStatement(parser: Parser): Node = +proc exprStatement(parser: Parser, inBlock: bool): Node = let expression = parser.expression() if expression != nil: result = Node(kind: nkExprStmt, expression: expression) else: parser.errorAtCurrent("Expression expected.") - parser.consume(tkSemicolon, "';' expected after expression statement.") + if parser.peekMatch(tkRightBrace) and inBlock: + result = Node(kind: nkExpr, expression: result.expression) # block should also check if it is the last expr. + else: + discard parser.consume(tkSemicolon, "';' expected after expression statement.") -proc statement(parser: Parser): Node = +proc statement(parser: Parser, inBlock: bool = false): Node = if parser.match(tkProc): # it is possibly a proc declaration, but # it could also be a proc expression @@ -423,17 +455,17 @@ proc statement(parser: Parser): Node = if parser.peekMatch(tkLeftParen): # proc expression - backtrack and let it go to expression statement parser.backtrack() - result = parser.exprStatement() + result = parser.exprStatement(inBlock) else: # proc definition - var declaration sort of code - parser.consume(tkIdentifier, "Procedure name expected after 'proc'.") + discard parser.consume(tkIdentifier, "Procedure name expected after 'proc'.") let varname = parser.previous.get().text - parser.consume(tkLeftParen, "'(' expected after procedure name.") + discard parser.consume(tkLeftParen, "'(' expected after procedure name.") let funct = parser.parseProcDeclaration() result = Node(kind: nkVarDecl, name: varname, value: funct) - parser.consume(tkSemicolon, "';' expected after procedure declaration.") + discard parser.consume(tkSemicolon, "';' expected after procedure declaration.") else: - result = parser.exprStatement() + result = parser.exprStatement(inBlock) if parser.panicMode: parser.synchronize() diff --git a/src/ndspkg/config.nim b/src/ndspkg/config.nim index e913bc5..dbdb0be 100644 --- a/src/ndspkg/config.nim +++ b/src/ndspkg/config.nim @@ -17,7 +17,7 @@ const debugClosures* = defined(debug) # specific closure debug switches type compMode* = enum cmOne, cmAst -const compilerChoice* = cmOne +const compilerChoice* = cmAst # choose a compiler: cmOne - version 1, deprecated # cmAst - version 2, but only use parser and print AST produced # cmOne will be removed once compv2 is done