block expressions, improved error reporting

This commit is contained in:
prod2 2022-12-03 09:32:41 +01:00
parent c36292843a
commit 1e139194b8
2 changed files with 72 additions and 40 deletions

View File

@ -93,10 +93,15 @@ proc match(parser: Parser, tokenTypes: set[TokenType]): bool =
else: else:
false false
proc consume(parser: Parser, tokenType: TokenType | set[TokenType], msg: string) = proc consume(parser: Parser, tokenType: TokenType | set[TokenType], msg: string): bool =
if not parser.match(tokenType): # 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.errorAtCurrent(msg)
parser.advance() # stop infinite loops false
proc peek(parser: Parser): Token = proc peek(parser: Parser): Token =
parser.current parser.current
@ -107,9 +112,9 @@ proc peekMatch(parser: Parser, tokenType: TokenType): bool =
proc synchronize(parser: Parser) = proc synchronize(parser: Parser) =
parser.panicMode = false parser.panicMode = false
while parser.current.tokenType != tkEof: while parser.current.tokenType != tkEof:
if parser.previous.get().tokenType in {tkSemicolon, tkRightBrace}: if parser.previous.get().tokenType in {tkSemicolon}:
return return
if parser.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile}: if parser.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile, tkRightBrace}:
return return
parser.advance() parser.advance()
@ -118,6 +123,7 @@ proc isAtEnd(parser: Parser): bool =
# EXPRESSIONS # EXPRESSIONS
proc expression(parser: Parser): Node proc expression(parser: Parser): Node
proc statement(parser: Parser, inBlock: bool = false): Node
# expressions, but not assignments # expressions, but not assignments
proc exprNonAssign(parser: Parser): Node proc exprNonAssign(parser: Parser): Node
@ -126,10 +132,10 @@ proc parseList(parser: Parser): Node =
while not parser.isAtEnd() and not parser.peekMatch(tkRightBracket): while not parser.isAtEnd() and not parser.peekMatch(tkRightBracket):
result.elems.add(parser.expression()) result.elems.add(parser.expression())
if parser.peek().tokenType != tkRightBracket: if parser.peek().tokenType != tkRightBracket and not parser.consume(tkComma, "',' expected after list elements."):
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 = proc parseTable(parser: Parser): Node =
result = Node(kind: nkTable, keys: @[], values: @[]) result = Node(kind: nkTable, keys: @[], values: @[])
@ -138,19 +144,19 @@ proc parseTable(parser: Parser): Node =
# [key] = syntax # [key] = syntax
if parser.match(tkLeftBracket): if parser.match(tkLeftBracket):
result.keys.add(parser.expression()) result.keys.add(parser.expression())
parser.consume(tkRightBracket, "']' expected after table key.") discard parser.consume(tkRightBracket, "']' expected after table key.")
# key = syntax # key = syntax
elif parser.match(tkIdentifier): elif parser.match(tkIdentifier):
result.keys.add(Node(kind: nkConst, constant: parser.previous.get().text.fromNimString())) result.keys.add(Node(kind: nkConst, constant: parser.previous.get().text.fromNimString()))
else: else:
parser.errorAtCurrent("Key expected (have you forgotten to put the key in brackets?).") 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()) result.values.add(parser.exprNonAssign())
if parser.peek().tokenType != tkRightBrace: if parser.peek().tokenType != tkRightBrace and not parser.consume(tkComma, "',' expected after table key value pair."):
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 = proc parseProcDeclaration(parser: Parser): Node =
# returns a nkProc, assumes that the left paren # returns a nkProc, assumes that the left paren
@ -160,16 +166,38 @@ proc parseProcDeclaration(parser: Parser): Node =
var params: seq[string] = @[] var params: seq[string] = @[]
while not parser.isAtEnd() and not parser.peekMatch(tkRightParen): 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) params.add(parser.previous.get().text)
if not parser.isAtEnd() and not parser.peekMatch(tkRightParen): if not parser.isAtEnd() and not parser.peekMatch(tkRightParen) and not parser.consume(tkComma, "',' expected between parameters."):
parser.consume(tkComma, "',' expected between parameters.") break
parser.consume(tkRightParen, "')' expected after parameter list.") discard parser.consume(tkRightParen, "')' expected after parameter list.")
let body = parser.expression() let body = parser.expression()
result = Node(kind: nkProc, parameters: params, procBody: body) 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 = proc primary(parser: Parser): Node =
if parser.match(tkFalse): if parser.match(tkFalse):
return Node(kind: nkConst, constant: ndFalse) 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])) return Node(kind: nkConst, constant: fromNimString(parser.previous.get().text[1..^2]))
if parser.match(tkLeftParen): if parser.match(tkLeftParen):
let grouped = parser.expression() let grouped = parser.expression()
parser.consume(tkRightParen, "Expect ')' after expression.") discard parser.consume(tkRightParen, "Expect ')' after expression.")
return Node(kind: nkExpr, expression: grouped) return Node(kind: nkExpr, expression: grouped)
if parser.match(tkLeftBrace):
return parser.parseBlock()
if parser.match(tkStartList): if parser.match(tkStartList):
return parser.parseList() return parser.parseList()
if parser.match(tkStartTable): if parser.match(tkStartTable):
@ -196,7 +226,7 @@ proc primary(parser: Parser): Node =
parser.hold = nil parser.hold = nil
return result return result
if parser.match(tkProc): if parser.match(tkProc):
parser.consume(tkLeftParen, "'(' expected after 'proc'.") discard parser.consume(tkLeftParen, "'(' expected after 'proc'.")
return parser.parseProcDeclaration() return parser.parseProcDeclaration()
parser.errorAtCurrent("Primary expected, but something else found.") parser.errorAtCurrent("Primary expected, but something else found.")
@ -206,10 +236,10 @@ proc parseArgList(parser: Parser): seq[Node] =
var args: seq[Node] = @[] var args: seq[Node] = @[]
while not parser.isAtEnd() and not parser.peekMatch(tkRightParen): while not parser.isAtEnd() and not parser.peekMatch(tkRightParen):
let arg = parser.expression() let arg = parser.expression()
if not parser.isAtEnd() and not parser.peekMatch(tkRightParen): if not parser.isAtEnd() and not parser.peekMatch(tkRightParen) and not parser.consume(tkComma, "',' expected between arguments."):
parser.consume(tkComma, "',' expected between arguments.") break
args.add(arg) args.add(arg)
parser.consume(tkRightParen, "')' expected after argument list.") discard parser.consume(tkRightParen, "')' expected after argument list.")
return args 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 # NOTE: :index is counted as a single identifier, so two identifiers after eachother will be handled here
if parser.previous.get().tokenType == tkLeftBracket: if parser.previous.get().tokenType == tkLeftBracket:
let index = parser.expression() 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) result = Node(kind: nkGetIndex, gCollection: result, gIndex: index)
elif parser.previous.get().tokenType == tkIdentifier: elif parser.previous.get().tokenType == tkIdentifier:
let identText = parser.previous.get().text let identText = parser.previous.get().text
@ -237,7 +268,8 @@ proc parseIndex(parser: Parser): Node =
result = Node(kind: nkColonCall, arguments: args, function: funct) result = Node(kind: nkColonCall, arguments: args, function: funct)
else: else:
# dot # 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())) result = Node(kind: nkGetIndex, gCollection: result, gIndex: Node(kind: nkConst, constant: parser.previous.get().text.fromNimString()))
proc parseCall(parser: Parser): Node = proc parseCall(parser: Parser): Node =
@ -248,9 +280,9 @@ proc parseCall(parser: Parser): Node =
proc parseIf(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() let cond = parser.expression()
parser.consume(tkRightParen, "')' expected after condition.") discard parser.consume(tkRightParen, "')' expected after condition.")
let body = parser.expression() let body = parser.expression()
result = Node(kind: nkIf, ifCondition: cond, ifBody: body) result = Node(kind: nkIf, ifCondition: cond, ifBody: body)
@ -258,9 +290,9 @@ proc parseIf(parser: Parser): Node =
result.elseBody = parser.expression() result.elseBody = parser.expression()
proc parseWhile(parser:Parser): Node = proc parseWhile(parser:Parser): Node =
parser.consume(tkLeftParen, "'(' expected after 'while'.") discard parser.consume(tkLeftParen, "'(' expected after 'while'.")
let cond = parser.expression() let cond = parser.expression()
parser.consume(tkRightParen, "')' expected after condition.") discard parser.consume(tkRightParen, "')' expected after condition.")
let body = parser.expression() let body = parser.expression()
result = Node(kind: nkWhile, whileCondition: cond, whileBody: body) result = Node(kind: nkWhile, whileCondition: cond, whileBody: body)
@ -404,18 +436,18 @@ proc expression(parser: Parser): Node =
parser.parseAmpersand() parser.parseAmpersand()
# STATEMENTS # STATEMENTS
proc exprStatement(parser: Parser, inBlock: bool): Node =
proc statement(parser: Parser): Node
proc exprStatement(parser: Parser): Node =
let expression = parser.expression() let expression = parser.expression()
if expression != nil: if expression != nil:
result = Node(kind: nkExprStmt, expression: expression) result = Node(kind: nkExprStmt, expression: expression)
else: else:
parser.errorAtCurrent("Expression expected.") 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): if parser.match(tkProc):
# it is possibly a proc declaration, but # it is possibly a proc declaration, but
# it could also be a proc expression # it could also be a proc expression
@ -423,17 +455,17 @@ proc statement(parser: Parser): Node =
if parser.peekMatch(tkLeftParen): if parser.peekMatch(tkLeftParen):
# proc expression - backtrack and let it go to expression statement # proc expression - backtrack and let it go to expression statement
parser.backtrack() parser.backtrack()
result = parser.exprStatement() result = parser.exprStatement(inBlock)
else: else:
# proc definition - var declaration sort of code # 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 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() let funct = parser.parseProcDeclaration()
result = Node(kind: nkVarDecl, name: varname, value: funct) result = Node(kind: nkVarDecl, name: varname, value: funct)
parser.consume(tkSemicolon, "';' expected after procedure declaration.") discard parser.consume(tkSemicolon, "';' expected after procedure declaration.")
else: else:
result = parser.exprStatement() result = parser.exprStatement(inBlock)
if parser.panicMode: if parser.panicMode:
parser.synchronize() parser.synchronize()

View File

@ -17,7 +17,7 @@ const debugClosures* = defined(debug) # specific closure debug switches
type compMode* = enum type compMode* = enum
cmOne, cmAst cmOne, cmAst
const compilerChoice* = cmOne const compilerChoice* = cmAst
# choose a compiler: cmOne - version 1, deprecated # choose a compiler: cmOne - version 1, deprecated
# cmAst - version 2, but only use parser and print AST produced # cmAst - version 2, but only use parser and print AST produced
# cmOne will be removed once compv2 is done # cmOne will be removed once compv2 is done