1353 lines
48 KiB
Nim
1353 lines
48 KiB
Nim
# Copyright 2024 Mattia Giambirtone & All Contributors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# 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.
|
|
|
|
## A recursive-descent top-down parser implementation
|
|
|
|
import std/strformat
|
|
import std/sequtils
|
|
import std/strutils
|
|
import std/tables
|
|
import std/os
|
|
|
|
|
|
import ast
|
|
import token
|
|
import errors
|
|
import config
|
|
import lexer
|
|
import util/symbols
|
|
|
|
|
|
export token, ast, errors
|
|
|
|
|
|
type
|
|
# Just a convenient alias
|
|
ParseTree* = seq[ASTNode]
|
|
## A parse tree
|
|
Precedence {.pure.} = enum
|
|
## Operator precedence
|
|
## clearly stolen from
|
|
## nim
|
|
Arrow = 0,
|
|
Assign,
|
|
Or,
|
|
And,
|
|
Compare,
|
|
Bitwise,
|
|
Addition,
|
|
Multiplication,
|
|
Power,
|
|
None # Used for stuff that isn't an operator
|
|
|
|
OperatorTable = ref object
|
|
## A table for storing and
|
|
## handling the precedence
|
|
## of operators
|
|
tokens: seq[string]
|
|
precedence: array[Precedence, seq[string]]
|
|
|
|
Parser* = ref object
|
|
## A recursive-descent top-down
|
|
## parser for the Peon programming
|
|
## language
|
|
|
|
# Index into self.tokens
|
|
current: int
|
|
# The name of the file being parsed.
|
|
# Only meaningful for parse errors
|
|
file: string
|
|
# The list of tokens representing
|
|
# the source code to be parsed
|
|
tokens: seq[Token]
|
|
# Just like scope depth tells us how
|
|
# many nested scopes are above us, the
|
|
# loop depth tells us how many nested
|
|
# loops are above us. It's just a simple
|
|
# way of statically detecting stuff like
|
|
# the break statement being used outside
|
|
# loops. Maybe a bit overkill for a parser?
|
|
loopDepth: int
|
|
# Stores the current scope depth (0 = global, > 0 local)
|
|
scopeDepth: int
|
|
# Operator table
|
|
operators: OperatorTable
|
|
# The AST we're producing
|
|
tree: seq[ASTNode]
|
|
# Stores line data
|
|
lines: seq[tuple[start, stop: int]]
|
|
# The source of the current module
|
|
source: string
|
|
# Keeps track of imported modules.
|
|
# The key is the module's fully qualified
|
|
# path, while the boolean indicates whether
|
|
# it has been fully loaded. This is useful
|
|
# to avoid importing a module twice and to
|
|
# detect recursive dependency cycles
|
|
modules: TableRef[string, bool]
|
|
|
|
ParseError* = ref object of PeonException
|
|
## A parsing exception
|
|
parser*: Parser
|
|
token*: Token
|
|
|
|
|
|
proc addOperator(self: OperatorTable, lexeme: string) =
|
|
## Adds an operator to the table. Its precedence
|
|
## is inferred from the operator's lexeme (the
|
|
## criteria are similar to Nim's)
|
|
if lexeme in self.tokens:
|
|
return # We've already added it!
|
|
var prec = Power
|
|
if lexeme.len() >= 2 and lexeme[^2..^1] in ["->", "~>", "=>"]:
|
|
prec = Arrow
|
|
elif lexeme == "and":
|
|
prec = Precedence.And
|
|
elif lexeme == "or":
|
|
prec = Precedence.Or
|
|
elif lexeme == "=" or lexeme.endsWith("=") and lexeme[0] notin {'<', '>', '!', '?', '~', '='}:
|
|
prec = Assign
|
|
elif lexeme[0] in {'$', } or lexeme == "**":
|
|
# TODO: Redundant?
|
|
prec = Power
|
|
elif lexeme[0] in {'*', '%', '/', '\\'}:
|
|
prec = Multiplication
|
|
elif lexeme[0] in {'+', '-'}:
|
|
prec = Addition
|
|
elif lexeme in [">>", "<<", "|", "~", "&", "^"]:
|
|
prec = Bitwise
|
|
elif lexeme[0] in {'<', '>', '=', '!'}:
|
|
prec = Compare
|
|
self.tokens.add(lexeme)
|
|
self.precedence[prec].add(lexeme)
|
|
|
|
|
|
proc newOperatorTable: OperatorTable =
|
|
## Initializes a new OperatorTable
|
|
## object
|
|
new(result)
|
|
result.tokens = @[]
|
|
for prec in Precedence:
|
|
result.precedence[prec] = @[]
|
|
# These operators are currently hardcoded
|
|
# due to compiler limitations
|
|
result.addOperator("=")
|
|
result.addOperator(".")
|
|
|
|
|
|
proc getPrecedence(self: OperatorTable, lexeme: string): Precedence =
|
|
## Gets the precedence of a given operator
|
|
for (prec, operators) in self.precedence.pairs():
|
|
if lexeme in operators:
|
|
return prec
|
|
return Precedence.None
|
|
|
|
|
|
proc newParser*: Parser =
|
|
## Initializes a new Parser object
|
|
new(result)
|
|
# Nim initializes all the other fields
|
|
# automatically
|
|
result.operators = newOperatorTable()
|
|
result.modules = newTable[string, bool]()
|
|
|
|
|
|
# Public getters for improved error formatting
|
|
proc getCurrent*(self: Parser): int {.inline.} = self.current
|
|
proc getCurrentToken*(self: Parser): Token {.inline.} = (if self.getCurrent() >=
|
|
self.tokens.high() or
|
|
self.getCurrent() - 1 < 0: self.tokens[^1] else: self.tokens[self.current - 1])
|
|
proc getSource*(self: Parser): string {.inline.} = self.source
|
|
template endOfFile: Token = Token(kind: EndOfFile, lexeme: "", line: -1)
|
|
template endOfLine(msg: string, tok: Token = nil) = self.expect(Semicolon, msg, tok)
|
|
|
|
|
|
# Utility functions
|
|
proc dispatch(self: Parser): ASTNode
|
|
|
|
proc beginScope(self: Parser) {.inline.} =
|
|
## Begins a new lexical scope
|
|
inc(self.scopeDepth)
|
|
|
|
|
|
proc endScope(self: Parser) {.inline.} =
|
|
## Ends a new lexical scope
|
|
dec(self.scopeDepth)
|
|
|
|
|
|
proc peek(self: Parser, distance: int = 0): Token {.inline.} =
|
|
## Peeks at the token at the given distance.
|
|
## If the distance is out of bounds, an EOF
|
|
## token is returned. A negative distance may
|
|
## be used to retrieve previously consumed
|
|
## tokens
|
|
if self.tokens.high() == -1 or self.current + distance > self.tokens.high() or self.current + distance < 0:
|
|
result = endOfFile
|
|
else:
|
|
result = self.tokens[self.current + distance]
|
|
|
|
|
|
proc done(self: Parser): bool {.inline.} =
|
|
## Returns true if we're at the
|
|
## end of the file. Note that the
|
|
## parser expects an explicit
|
|
## EOF token to signal the end
|
|
## of the file
|
|
result = self.peek().kind == EndOfFile
|
|
|
|
|
|
proc step(self: Parser): Token {.inline.} =
|
|
## Consumes a token and returns it
|
|
if self.done():
|
|
result = self.peek()
|
|
else:
|
|
result = self.tokens[self.current]
|
|
self.current += 1
|
|
|
|
|
|
proc error(self: Parser, message: string, token: Token = nil) =
|
|
## Raises a ParseError exception
|
|
var token = if token.isNil(): self.peek() else: token
|
|
if token.kind == EndOfFile:
|
|
token = self.peek(-1)
|
|
raise ParseError(msg: message, token: token, line: token.line, file: self.file, parser: self)
|
|
|
|
|
|
# Why do we allow strings or enum members of TokenType? Well, it's simple:
|
|
# symbols like ":" and "=" are both valid operators (and therefore they are
|
|
# tokenized as symbols), but they are also used in a context where they are just
|
|
# separators (for example, the colon is used in type declarations). Since we can't
|
|
# tell at tokenization time which of the two contexts we're in, we just treat everything
|
|
# as a symbol and in the cases where we need a specific token we just match the string
|
|
# directly
|
|
proc check(self: Parser, kind: TokenType, distance: int = 0): bool {.inline.} =
|
|
## Checks if the given token at the given distance
|
|
## matches the expected kind and returns a boolean.
|
|
## The distance parameter is passed directly to
|
|
## self.peek()
|
|
self.peek(distance).kind == kind
|
|
|
|
|
|
proc check(self: Parser, kind: string, distance: int = 0): bool {.inline.} =
|
|
## Checks if the given token at the given distance
|
|
## matches the expected kind and returns a boolean.
|
|
## The distance parameter is passed directly to
|
|
## self.peek()
|
|
self.peek(distance).lexeme == kind
|
|
|
|
|
|
proc check(self: Parser, kind: openarray[TokenType]): bool {.inline.} =
|
|
## Calls self.check() in a loop with each element of
|
|
## the given openarray of token kinds and returns
|
|
## at the first match. Note that this assumes
|
|
## that only one token may match at a given
|
|
## position
|
|
for k in kind:
|
|
if self.check(k):
|
|
return true
|
|
return false
|
|
|
|
|
|
proc match(self: Parser, kind: TokenType): bool {.inline.} =
|
|
## Behaves like self.check(), except that when a token
|
|
## matches it is also consumed
|
|
if self.check(kind):
|
|
discard self.step()
|
|
result = true
|
|
else:
|
|
result = false
|
|
|
|
|
|
proc match(self: Parser, kind: openarray[TokenType]): bool {.inline.} =
|
|
## Calls self.match() in a loop with each element of
|
|
## the given openarray of token kinds and returns
|
|
## at the first match. Note that this assumes
|
|
## that only one token may exist at a given
|
|
## position
|
|
for k in kind:
|
|
if self.match(k):
|
|
return true
|
|
result = false
|
|
|
|
|
|
proc expect(self: Parser, kind: TokenType, message: string = "", token: Token = nil) {.inline.} =
|
|
## Behaves like self.match(), except that
|
|
## when a token doesn't match, an error
|
|
## is raised. If no error message is
|
|
## given, a default one is used
|
|
if not self.match(kind):
|
|
if message.len() == 0:
|
|
self.error(&"expecting token of kind {kind}, found {self.peek().kind} instead", token)
|
|
else:
|
|
self.error(message)
|
|
|
|
|
|
proc expect(self: Parser, kind: openarray[TokenType], message: string = "", token: Token = nil) {.inline, used.} =
|
|
## Behaves like self.expect(), except that
|
|
## an error is raised only if none of the
|
|
## given token kinds matches
|
|
for k in kind:
|
|
if self.match(kind):
|
|
return
|
|
if message.len() == 0:
|
|
self.error(&"""expecting any of the following tokens: {kind.join(", ")}, but got {self.peek().kind} instead""", token)
|
|
|
|
|
|
proc check(self: Parser, kind: openarray[string]): bool {.inline.} =
|
|
## Calls self.check() in a loop with each element of
|
|
## the given openarray of strings and returns
|
|
## at the first match. Note that this assumes
|
|
## that only one token may match at a given
|
|
## position
|
|
for k in kind:
|
|
if self.check(k):
|
|
return true
|
|
return false
|
|
|
|
|
|
proc match(self: Parser, kind: string): bool {.inline.} =
|
|
## Behaves like self.check(), except that when a string
|
|
## matches it is also consumed
|
|
if self.check(kind):
|
|
discard self.step()
|
|
result = true
|
|
else:
|
|
result = false
|
|
|
|
|
|
proc match(self: Parser, kind: openarray[string]): bool {.inline.} =
|
|
## Calls self.match() in a loop with each element of
|
|
## the given openarray of strings and returns
|
|
## at the first match. Note that this assumes
|
|
## that only one token may exist at a given
|
|
## position
|
|
for k in kind:
|
|
if self.match(k):
|
|
return true
|
|
result = false
|
|
|
|
|
|
proc expect(self: Parser, kind: string, message: string = "", token: Token = nil) {.inline.} =
|
|
## Behaves like self.match(), except that
|
|
## when a string doesn't match, an error
|
|
## is raised. If no error message is
|
|
## given, a default one is used
|
|
if not self.match(kind):
|
|
if message.len() == 0:
|
|
self.error(&"expecting token of kind {kind}, found {self.peek().kind} instead", token)
|
|
else:
|
|
self.error(message)
|
|
|
|
|
|
proc expect(self: Parser, kind: openarray[string], message: string = "", token: Token = nil) {.inline, used.} =
|
|
## Behaves like self.expect(), except that
|
|
## an error is raised only if none of the
|
|
## given strings matches
|
|
for k in kind:
|
|
if self.match(kind):
|
|
return
|
|
if message.len() == 0:
|
|
self.error(&"""expecting any of the following tokens: {kind.join(", ")}, but got {self.peek().kind} instead""", token)
|
|
|
|
|
|
# Forward declarations
|
|
proc expression(self: Parser): Expression
|
|
proc expressionStatement(self: Parser): Statement
|
|
proc statement(self: Parser): Statement
|
|
proc varDecl(self: Parser): Declaration
|
|
proc parseFunExpr(self: Parser): LambdaExpr
|
|
proc funDecl(self: Parser, isOperator: bool = false): FunDecl
|
|
proc declaration(self: Parser): Declaration
|
|
proc parse*(self: Parser, tokens: seq[Token], file: string, lines: seq[tuple[start, stop: int]], source: string, persist: bool = false): seq[ASTNode]
|
|
proc findOperators(self: Parser, tokens: seq[Token])
|
|
proc parseOr(self: Parser): Expression
|
|
|
|
|
|
# Top-down parsing handlers
|
|
|
|
proc primary(self: Parser): Expression =
|
|
## Parses primary expressions. A primary
|
|
## expression produces a value of a built-in
|
|
## type (for example integer literals, lambdas,
|
|
## coroutines, etc.)
|
|
case self.peek().kind:
|
|
of True:
|
|
result = newTrueExpr(self.step())
|
|
of False:
|
|
result = newFalseExpr(self.step())
|
|
of Float:
|
|
result = newFloatExpr(self.step())
|
|
of Integer:
|
|
result = newIntExpr(self.step())
|
|
of Identifier:
|
|
result = newIdentExpr(self.step())
|
|
of LeftParen:
|
|
let tok = self.step()
|
|
result = newGroupingExpr(self.expression(), tok)
|
|
self.expect(RightParen, "unterminated parenthesized expression")
|
|
of RightParen, RightBracket, RightBrace:
|
|
# This is *technically* unnecessary: the parser would
|
|
# throw an error regardless, but it's a little bit nicer
|
|
# when the error message is more specific
|
|
self.error(&"unmatched '{self.peek().lexeme}'")
|
|
of Hex:
|
|
result = newHexExpr(self.step())
|
|
of Octal:
|
|
result = newOctExpr(self.step())
|
|
of Binary:
|
|
result = newBinExpr(self.step())
|
|
of String:
|
|
result = newStrExpr(self.step())
|
|
of Char:
|
|
result = newCharExpr(self.step())
|
|
of TokenType.Inf:
|
|
result = newInfExpr(self.step())
|
|
of TokenType.Nan:
|
|
result = newNanExpr(self.step())
|
|
# We only allow expressions with precedence lower than assignment
|
|
# inside ref/ptr/lent/const expressions because this allows us to
|
|
# parse variable declarations such as var x: ref type = value; without
|
|
# having the code to parse just the type declaration also capture the
|
|
# assignment
|
|
of TokenType.Ref:
|
|
let tok = self.step()
|
|
result = newRefExpr(self.parseOr(), tok)
|
|
of TokenType.Ptr:
|
|
let tok = self.step()
|
|
result = newPtrExpr(self.parseOr(), tok)
|
|
of TokenType.Lent:
|
|
let tok = self.step()
|
|
result = newLentExpr(self.parseOr(), tok)
|
|
of TokenType.Const:
|
|
let tok = self.step()
|
|
result = newConstExpr(self.parseOr(), tok)
|
|
else:
|
|
self.error("invalid syntax")
|
|
result.file = self.file
|
|
|
|
|
|
proc makeCall(self: Parser, callee: Expression): CallExpr =
|
|
## Utility function called iteratively by self.call()
|
|
## to parse a function call
|
|
let tok = self.peek(-1)
|
|
var argNames: seq[IdentExpr] = @[]
|
|
var arguments: tuple[positionals: seq[Expression], keyword: TableRef[string, tuple[name: IdentExpr, value: Expression]]] = (positionals: @[],
|
|
keyword: newTable[string, tuple[name: IdentExpr, value: Expression]]())
|
|
var argument: Expression = nil
|
|
var argCount = 0
|
|
if not self.check(RightParen):
|
|
while true:
|
|
if argCount >= 255:
|
|
self.error("cannot pass more than 255 arguments in call")
|
|
argument = self.expression()
|
|
if argument.kind == assignExpr:
|
|
var assign = AssignExpr(argument)
|
|
if assign.name in argNames:
|
|
self.error("duplicate keyword arguments are not allowed", assign.name.token)
|
|
argNames.add(assign.name)
|
|
arguments.keyword[assign.name.token.lexeme] = (name: assign.name, value: assign.value)
|
|
elif arguments.keyword.len() == 0:
|
|
arguments.positionals.add(argument)
|
|
else:
|
|
self.error("positional argument cannot follow keyword argument in call", token=argument.token)
|
|
if not self.match(Comma):
|
|
break
|
|
argCount += 1
|
|
self.expect(RightParen)
|
|
result = newCallExpr(callee, arguments, tok)
|
|
result.file = self.file
|
|
result.closeParen = self.peek(-1)
|
|
|
|
|
|
proc parseGenericArgs(self: Parser): Expression =
|
|
## Parses generic instantiation expressions
|
|
var item = newIdentExpr(self.peek(-2))
|
|
var types: seq[Expression] = @[]
|
|
while not self.check(RightBracket) and not self.done():
|
|
types.add(self.expression())
|
|
if not self.match(Comma):
|
|
break
|
|
self.expect(RightBracket)
|
|
return newGenericExpr(item, types)
|
|
|
|
|
|
proc call(self: Parser): Expression =
|
|
## Parses function calls and object field
|
|
## accessing
|
|
result = self.primary()
|
|
while true:
|
|
if self.match(LeftParen):
|
|
result = self.makeCall(result)
|
|
elif self.match(Dot):
|
|
self.expect(Identifier, "expecting attribute name after '.'")
|
|
result = newGetterExpr(result, newIdentExpr(self.peek(-1)), self.peek(-1))
|
|
result.file = self.file
|
|
elif self.match(LeftBracket):
|
|
if self.peek(-2).kind != Identifier:
|
|
self.error("expecting identifier before '['")
|
|
result = self.parseGenericArgs()
|
|
else:
|
|
break
|
|
|
|
## Operator parsing handlers
|
|
|
|
proc parseUnary(self: Parser): Expression =
|
|
## Parses unary expressions
|
|
if self.check([Identifier, Symbol]) and self.peek().lexeme in self.operators.tokens:
|
|
result = newUnaryExpr(self.step(), self.parseUnary())
|
|
result.file = self.file
|
|
else:
|
|
result = self.call()
|
|
|
|
|
|
proc parsePow(self: Parser): Expression =
|
|
## Parses power expressions
|
|
result = self.parseUnary()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Power:
|
|
operator = self.step()
|
|
right = self.parseUnary()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseMul(self: Parser): Expression =
|
|
## Parses multiplication and division
|
|
## expressions
|
|
result = self.parsePow()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Multiplication:
|
|
operator = self.step()
|
|
right = self.parsePow()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseAdd(self: Parser): Expression =
|
|
## Parses addition and subtraction
|
|
## expressions
|
|
result = self.parseMul()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Addition:
|
|
operator = self.step()
|
|
right = self.parseMul()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseBitwise(self: Parser): Expression =
|
|
## Parses bitwise expressions
|
|
result = self.parseAdd()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Bitwise:
|
|
operator = self.step()
|
|
right = self.parseAdd()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseCmp(self: Parser): Expression =
|
|
## Parses comparison expressions
|
|
result = self.parseBitwise()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Compare:
|
|
operator = self.step()
|
|
right = self.parseAdd()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseAnd(self: Parser): Expression =
|
|
## Parses logical and expressions
|
|
result = self.parseCmp()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == And:
|
|
operator = self.step()
|
|
right = self.parseCmp()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseOr(self: Parser): Expression =
|
|
## Parses logical or expressions
|
|
result = self.parseAnd()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Or:
|
|
operator = self.step()
|
|
right = self.parseAnd()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
proc parseAssign(self: Parser): Expression =
|
|
## Parses assignment expressions
|
|
result = self.parseOr()
|
|
if self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Assign:
|
|
let tok = self.step()
|
|
var value = self.expression()
|
|
case result.kind:
|
|
of identExpr, sliceExpr:
|
|
result = newAssignExpr(IdentExpr(result), value, tok)
|
|
result.file = self.file
|
|
of getterExpr:
|
|
result = newSetterExpr(GetterExpr(result).obj, GetterExpr(result).name, value, tok)
|
|
result.file = self.file
|
|
else:
|
|
self.error("invalid assignment target", result.token)
|
|
|
|
|
|
proc parseArrow(self: Parser): Expression =
|
|
## Parses arrow expressions
|
|
result = self.parseAssign()
|
|
var operator: Token
|
|
var right: Expression
|
|
while self.check([Identifier, Symbol]) and self.operators.getPrecedence(self.peek().lexeme) == Arrow:
|
|
operator = self.step()
|
|
right = self.parseAssign()
|
|
result = newBinaryExpr(result, operator, right)
|
|
result.file = self.file
|
|
|
|
|
|
## Statement parsing handlers
|
|
|
|
proc assertStmt(self: Parser): Statement =
|
|
## Parses "assert" statements, which
|
|
## raise an error if the expression
|
|
## fed into them is false
|
|
let tok = self.peek(-1)
|
|
var expression = self.expression()
|
|
endOfLine("missing semicolon after 'assert'")
|
|
result = newAssertStmt(expression, tok)
|
|
result.file = self.file
|
|
|
|
|
|
proc blockStmt(self: Parser): BlockStmt =
|
|
## Parses block statements. A block
|
|
## statement simply opens a new local
|
|
## scope
|
|
self.beginScope()
|
|
let tok = self.peek(-1)
|
|
var
|
|
body: seq[ASTNode] = @[]
|
|
node: ASTNode
|
|
while not self.check(RightBrace) and not self.done():
|
|
node = self.dispatch()
|
|
if node.isNil():
|
|
continue
|
|
body.add(node)
|
|
self.expect(RightBrace, "expecting '}'")
|
|
result = newBlockStmt(body, tok)
|
|
result.file = self.file
|
|
self.endScope()
|
|
|
|
|
|
proc namedBlockStmt(self: Parser): Statement =
|
|
## Parses named block statement
|
|
self.beginScope()
|
|
let tok = self.peek(-1)
|
|
self.expect(Identifier, "expecting block name after 'block'")
|
|
var name = newIdentExpr(self.peek(-1))
|
|
name.file = self.file
|
|
inc(self.loopDepth)
|
|
self.expect(LeftBrace, "expecting '{' after block name")
|
|
var
|
|
body: seq[ASTNode] = @[]
|
|
node: ASTNode
|
|
while not self.check(RightBrace) and not self.done():
|
|
node = self.dispatch()
|
|
if node.isNil():
|
|
continue
|
|
body.add(node)
|
|
self.expect(RightBrace, "expecting '}'")
|
|
result = newNamedBlockStmt(body, name, tok)
|
|
result.file = self.file
|
|
self.endScope()
|
|
dec(self.loopDepth)
|
|
|
|
|
|
proc breakStmt(self: Parser): Statement =
|
|
## Parses break statements
|
|
let tok = self.peek(-1)
|
|
var label: IdentExpr
|
|
if self.loopDepth == 0:
|
|
self.error("'break' cannot be used outside loops")
|
|
if self.match(Identifier):
|
|
label = newIdentExpr(self.peek(-1))
|
|
label.file = self.file
|
|
endOfLine("missing semicolon after 'break'")
|
|
result = newBreakStmt(tok, label)
|
|
result.file = self.file
|
|
|
|
|
|
proc continueStmt(self: Parser): Statement =
|
|
## Parses continue statements
|
|
let tok = self.peek(-1)
|
|
var label: IdentExpr
|
|
if self.loopDepth == 0:
|
|
self.error("'continue' cannot be used outside loops")
|
|
if self.match(Identifier):
|
|
label = newIdentExpr(self.peek(-1))
|
|
label.file = self.file
|
|
endOfLine("missing semicolon after 'continue'")
|
|
result = newContinueStmt(tok, label)
|
|
result.file = self.file
|
|
|
|
|
|
proc returnStmt(self: Parser): Statement =
|
|
## Parses return statements
|
|
let tok = self.peek(-1)
|
|
var value: Expression
|
|
if not self.check(Semicolon):
|
|
# Since return can be used on its own too
|
|
# we need to check if there's an actual value
|
|
# to return or not
|
|
value = self.expression()
|
|
endOfLine("missing semicolon after 'return'")
|
|
result = newReturnStmt(value, tok)
|
|
result.file = self.file
|
|
|
|
|
|
proc forEachStmt(self: Parser): Statement =
|
|
## Parses C#-like foreach loops
|
|
let tok = self.peek(-1)
|
|
inc(self.loopDepth)
|
|
self.expect(Identifier)
|
|
let identifier = newIdentExpr(self.peek(-1))
|
|
self.expect("in")
|
|
let expression = self.expression()
|
|
self.expect(LeftBrace)
|
|
result = newForEachStmt(identifier, expression, self.blockStmt(), tok)
|
|
result.file = self.file
|
|
dec(self.loopDepth)
|
|
|
|
|
|
proc importStmt(self: Parser): Statement =
|
|
## Parses import statements. This is a little
|
|
## convoluted because we need to pre-parse the
|
|
## module to import the operators from it
|
|
if self.scopeDepth > 0:
|
|
self.error("import statements are only allowed at the top level")
|
|
var
|
|
tok = self.peek(-1)
|
|
moduleName = ""
|
|
names: seq[IdentExpr]
|
|
while not self.check(Semicolon) and not self.done():
|
|
if self.match(".."):
|
|
if not self.check("/"):
|
|
self.error("expecting '/' after '..' in import statement")
|
|
moduleName &= "../"
|
|
elif self.match("/"):
|
|
self.expect(Identifier, "expecting identifier after '/' in import statement")
|
|
moduleName &= &"/{self.peek(-1).lexeme}"
|
|
elif self.match(Identifier):
|
|
moduleName &= self.peek(-1).lexeme
|
|
else:
|
|
break
|
|
while not self.check(Semicolon) and not self.done():
|
|
self.expect(Identifier, "expecting identifier after 'import'")
|
|
names.add(newIdentExpr(self.peek(-1)))
|
|
if not self.match(Comma):
|
|
break
|
|
endOfLine("missing semicolon after import statement")
|
|
result = newImportStmt(newIdentExpr(Token(kind: Identifier, lexeme: moduleName,
|
|
line: self.peek(-1).line,
|
|
pos: (tok.pos.stop + 1, (tok.pos.stop + 1) + len(moduleName)),
|
|
relPos: (tok.relPos.stop + 1, (tok.relPos.stop + 1) + len(moduleName)))), names, tok)
|
|
result.file = self.file
|
|
moduleName &= ".pn"
|
|
var lexer = newLexer()
|
|
lexer.fillSymbolTable()
|
|
var path = ""
|
|
for i, searchPath in moduleLookupPaths:
|
|
if searchPath == "":
|
|
path = absolutePath(joinPath(splitPath(self.file).head, moduleName))
|
|
else:
|
|
path = joinPath(searchPath, moduleName)
|
|
if fileExists(path):
|
|
break
|
|
elif i == moduleLookupPaths.high():
|
|
self.error(&"""could not import '{path}': module not found""")
|
|
if not self.modules.getOrDefault(path, true):
|
|
self.error(&"coult not import '{path}' from '{self.file}' due to a cyclic dependency")
|
|
else:
|
|
self.modules[path] = false
|
|
try:
|
|
var source = readFile(path)
|
|
var tree = self.tree
|
|
var current = self.current
|
|
var tokens = self.tokens
|
|
var src = self.source
|
|
var file = self.file
|
|
discard self.parse(lexer.lex(source, path), file=path, source=source, lines=lexer.getLines(), persist=true)
|
|
self.file = file
|
|
self.source = src
|
|
self.tree = tree
|
|
self.current = current
|
|
self.tokens = tokens
|
|
# Module has been fully loaded and can now be used
|
|
self.modules[path] = true
|
|
except IOError:
|
|
self.error(&"could not import '{path}': {getCurrentExceptionMsg()}")
|
|
except OSError:
|
|
self.error(&"could not import '{path}': {getCurrentExceptionMsg()} [errno {osLastError()}]")
|
|
|
|
|
|
proc whileStmt(self: Parser): Statement =
|
|
## Parses a C-style while loop statement
|
|
let tok = self.peek(-1)
|
|
self.beginScope()
|
|
inc(self.loopDepth)
|
|
let condition = self.expression()
|
|
self.expect(LeftBrace)
|
|
result = newWhileStmt(condition, self.blockStmt(), tok)
|
|
result.file = self.file
|
|
self.endScope()
|
|
dec(self.loopDepth)
|
|
|
|
|
|
proc ifStmt(self: Parser): Statement =
|
|
## Parses if statements
|
|
let tok = self.peek(-1)
|
|
let condition = self.expression()
|
|
self.expect(LeftBrace)
|
|
let thenBranch = self.blockStmt()
|
|
var elseBranch: Statement
|
|
if self.match(Else):
|
|
if self.match(If):
|
|
elseBranch = self.ifStmt()
|
|
else:
|
|
self.expect(LeftBrace, "expecting 'if' or block statement")
|
|
elseBranch = self.blockStmt()
|
|
result = newIfStmt(condition, thenBranch, elseBranch, tok)
|
|
result.file = self.file
|
|
|
|
|
|
proc exportStmt(self: Parser): Statement =
|
|
## Parses export statements
|
|
var exported: IdentExpr
|
|
let tok = self.peek(-1)
|
|
if not self.match(Identifier):
|
|
self.error("expecting identifier after 'export' in export statement")
|
|
exported = newIdentExpr(self.peek(-1))
|
|
endOfLine("missing semicolon after 'raise'")
|
|
result = newExportStmt(exported, tok)
|
|
result.file = self.file
|
|
|
|
|
|
template checkDecl(self: Parser, isPrivate: bool) =
|
|
## Handy utility template that avoids us from copy
|
|
## pasting the same checks to all declaration handlers
|
|
if not isPrivate and self.scopeDepth > 0:
|
|
self.error("cannot bind public names inside local scopes")
|
|
|
|
|
|
proc parsePragmas(self: Parser): seq[Pragma] =
|
|
## Parses pragmas
|
|
var
|
|
name: IdentExpr
|
|
args: seq[LiteralExpr]
|
|
exp: Expression
|
|
names: seq[string]
|
|
while not self.match("]") and not self.done():
|
|
args = @[]
|
|
self.expect(Identifier, "expecting pragma name (did you forget a closing bracket?)")
|
|
if self.peek(-1).lexeme in names:
|
|
self.error("duplicate pragmas are not allowed")
|
|
names.add(self.peek(-1).lexeme)
|
|
name = newIdentExpr(self.peek(-1))
|
|
name.file = self.file
|
|
if self.match("]"):
|
|
result.add(newPragma(name, @[]))
|
|
break
|
|
# Pragma takes more than one argument, so they need
|
|
# to be parenthesized to avoid ambiguity
|
|
elif self.match("("):
|
|
while not self.match(")") and not self.done():
|
|
exp = self.primary()
|
|
if not exp.isConst():
|
|
self.error("pragma arguments can only be literals", exp.token)
|
|
args.add(LiteralExpr(exp))
|
|
if not self.match(","):
|
|
break
|
|
self.expect(LeftParen, "unterminated parenthesis in pragma arguments")
|
|
elif self.match(":"):
|
|
exp = self.primary()
|
|
if not exp.isConst():
|
|
self.error("pragma arguments can only be literals", exp.token)
|
|
args.add(LiteralExpr(exp))
|
|
result.add(newPragma(name, args))
|
|
result[^1].file = self.file
|
|
if self.match(","):
|
|
continue
|
|
|
|
|
|
proc varDecl(self: Parser): Declaration =
|
|
## Parses variable declarations
|
|
var tok = self.peek(-1)
|
|
self.expect(Identifier, &"expecting identifier after '{tok.lexeme}'")
|
|
var
|
|
name = newIdentExpr(self.peek(-1))
|
|
value: Expression
|
|
valueType: Expression
|
|
let isPrivate = not self.match("*")
|
|
self.checkDecl(isPrivate)
|
|
var pragmas: seq[Pragma] = @[]
|
|
if self.match(":"):
|
|
valueType = self.parseOr()
|
|
if self.match("="):
|
|
value = self.expression()
|
|
if value.isNil() and tok.kind == TokenType.Let:
|
|
self.error("let declaration requires an initializer")
|
|
self.expect(Semicolon, &"expecting semicolon after '{tok.lexeme}' declaration")
|
|
if self.match(TokenType.Pragma):
|
|
for pragma in self.parsePragmas():
|
|
pragmas.add(pragma)
|
|
case tok.kind:
|
|
of TokenType.Var:
|
|
result = newVarDecl(name, valueType=valueType, value=value, isPrivate=isPrivate, token=tok, pragmas=pragmas, mutable=true)
|
|
of TokenType.Const:
|
|
if not value.isConst():
|
|
self.error("constant initializer is not a constant")
|
|
result = newVarDecl(name, valueType=valueType, value=value, isPrivate=isPrivate, token=tok, constant=true, pragmas=pragmas)
|
|
of TokenType.Let:
|
|
result = newVarDecl(name, valueType=valueType, value=value, isPrivate=isPrivate, token=tok, pragmas=pragmas)
|
|
else:
|
|
discard # Unreachable
|
|
result.file = self.file
|
|
|
|
|
|
proc parseDeclParams(self: Parser, parameters: Parameters) =
|
|
## Helper to parse declaration parameters and avoid code duplication
|
|
var
|
|
ident: IdentExpr
|
|
valueType: Expression
|
|
default: Expression
|
|
metDefaults = false
|
|
i = 0
|
|
params: seq[Parameter] = @[]
|
|
while not self.check(RightParen):
|
|
if parameters.len() > 255:
|
|
self.error("cannot have more than 255 arguments in function declaration", self.peek(-1))
|
|
self.expect(Identifier, "expecting parameter name")
|
|
ident = newIdentExpr(self.peek(-1))
|
|
ident.file = self.file
|
|
if self.match(":"):
|
|
valueType = self.expression()
|
|
# This makes it so that a, b: int becomes a: int, b: int
|
|
var n = 0
|
|
while n < i:
|
|
if params[n].valueType.isNil():
|
|
params[n].valueType = valueType
|
|
else:
|
|
break
|
|
inc(n)
|
|
if ident.token.lexeme in parameters:
|
|
self.error("duplicate parameter name in function declaration is not allowed", ident.token)
|
|
if self.match("="):
|
|
default = self.expression()
|
|
metDefaults = true
|
|
else:
|
|
default = nil
|
|
if default.isNil() and metDefaults:
|
|
self.error("positional argument cannot follow default argument in function declaration", ident.token)
|
|
parameters[ident.token.lexeme] = Parameter(ident: ident, valueType: valueType, default: default)
|
|
params.add(parameters[ident.token.lexeme])
|
|
if not self.match(Comma):
|
|
break
|
|
inc(i)
|
|
self.expect(RightParen)
|
|
for parameter in parameters.values():
|
|
if parameter.valueType.isNil():
|
|
self.error(&"missing type declaration for '{parameter.ident.token.lexeme}' in function declaration")
|
|
|
|
|
|
proc parseFunExpr(self: Parser): LambdaExpr =
|
|
## Parses the return value of a function
|
|
## when it is another function. Works
|
|
## recursively
|
|
let tok = self.peek(-1)
|
|
result = newLambdaExpr(token=tok)
|
|
if self.match(LeftParen):
|
|
self.parseDeclParams(result.parameters)
|
|
if self.match(":"):
|
|
if self.match([Function, Coroutine, Generator]):
|
|
result.returnType = self.parseFunExpr()
|
|
else:
|
|
result.returnType = self.expression()
|
|
result.file = self.file
|
|
|
|
|
|
proc parseGenericConstraint(self: Parser, endToken: TokenType or string): Expression =
|
|
## Recursively parses a generic constraint
|
|
## and returns it as an expression
|
|
result = self.expression()
|
|
if not self.check(endToken):
|
|
case self.peek().lexeme:
|
|
of "|":
|
|
result = newBinaryExpr(result, self.step(), self.parseGenericConstraint(endToken))
|
|
result.file = self.file
|
|
of "~":
|
|
result = newUnaryExpr(self.step(), result)
|
|
result.file = self.file
|
|
of ",":
|
|
discard # Comma is handled in parseGenerics()
|
|
else:
|
|
self.error("invalid type constraint in generic declaration")
|
|
|
|
|
|
proc parseGenerics(self: Parser, decl: Declaration) =
|
|
## Parses generics in declarations
|
|
var
|
|
ident: IdentExpr
|
|
constr: Expression
|
|
if self.match("<"):
|
|
while not self.check(">") and not self.done():
|
|
self.expect(Identifier, "expecting generic type name")
|
|
ident = newIdentExpr(self.peek(-1))
|
|
ident.file = self.file
|
|
if self.match(":"):
|
|
constr = self.parseGenericConstraint(">")
|
|
else:
|
|
constr = nil
|
|
decl.genericTypes[ident.token.lexeme] = TypeGeneric(ident: ident, constr: constr)
|
|
if not self.match(Comma):
|
|
break
|
|
self.expect(">", "unterminated generic declaration")
|
|
if self.match(LeftBracket):
|
|
while not self.check(RightBracket) and not self.done():
|
|
self.expect(Identifier, "expecting generic type name")
|
|
ident = newIdentExpr(self.peek(-1))
|
|
ident.file = self.file
|
|
if self.match(":"):
|
|
constr = self.parseGenericConstraint(RightBracket)
|
|
else:
|
|
constr = nil
|
|
decl.genericValues[ident.token.lexeme] = TypeGeneric(ident: ident, constr: constr)
|
|
if not self.match(Comma):
|
|
break
|
|
self.expect(RightBracket, "unterminated generic declaration")
|
|
|
|
|
|
proc funDecl(self: Parser, isOperator: bool = false): FunDecl =
|
|
## Parses named function declarations
|
|
self.expect(Identifier, "expecting function name")
|
|
let name = self.peek(-1)
|
|
var
|
|
parameters: Parameters = newOrderedTable[string, Parameter]()
|
|
returnType: Expression
|
|
function = newFunDecl(newIdentExpr(name), parameters, nil, true, name, @[], returnType)
|
|
function.file = self.file
|
|
if self.match("*"):
|
|
function.isPrivate = true
|
|
self.checkDecl(function.isPrivate)
|
|
if self.check(["<", "["]):
|
|
self.parseGenerics(function)
|
|
if self.match(LeftParen):
|
|
var parameter: tuple[name: IdentExpr, valueType: Expression]
|
|
self.parseDeclParams(parameters)
|
|
if self.match(":"):
|
|
# Function returns a value
|
|
if self.match([Function, Coroutine, Generator]):
|
|
# The function's return type is another
|
|
# function. We specialize this case because
|
|
# the type declaration for a function lacks
|
|
# the braces that would qualify it as an
|
|
# expression
|
|
returnType = self.parseFunExpr()
|
|
else:
|
|
returnType = self.expression()
|
|
if not self.match(Semicolon):
|
|
# If we don't find a semicolon,
|
|
# it's not a forward declaration
|
|
self.expect(LeftBrace)
|
|
if self.match(TokenType.Pragma):
|
|
for pragma in self.parsePragmas():
|
|
function.pragmas.add(pragma)
|
|
function.body = self.blockStmt()
|
|
else:
|
|
# This is a forward declaration, so we keep the
|
|
# function body null so the compiler knows to treat
|
|
# it as such
|
|
if self.match(TokenType.Pragma):
|
|
for pragma in self.parsePragmas():
|
|
function.pragmas.add(pragma)
|
|
function.returnType = returnType
|
|
result = function
|
|
if isOperator:
|
|
if parameters.len() == 0:
|
|
self.error("cannot declare operator without arguments")
|
|
if function.returnType.isNil():
|
|
self.error("cannot declare void operator")
|
|
result.file = self.file
|
|
|
|
|
|
proc expression(self: Parser): Expression =
|
|
## Parses expressions
|
|
result = self.parseArrow() # Highest-level expression
|
|
result.file = self.file
|
|
|
|
|
|
proc expressionStatement(self: Parser): Statement =
|
|
## Parses expression statements, which
|
|
## are expressions followed by a semicolon
|
|
var expression = self.expression()
|
|
endOfLine("missing semicolon at end of expression", expression.token)
|
|
result = Statement(newExprStmt(expression, expression.token))
|
|
result.file = self.file
|
|
|
|
|
|
proc switchStmt(self: Parser): Statement =
|
|
## Parses switch statements
|
|
let tok = self.peek(-1)
|
|
let switch = self.expression()
|
|
self.expect(TokenType.LeftBrace, "expecting '{' after switch condition")
|
|
var branches: seq[tuple[cond: Expression, body: BlockStmt]] = @[]
|
|
var match: Expression
|
|
var body: BlockStmt
|
|
var default: BlockStmt
|
|
while not self.check([TokenType.RightBrace, TokenType.Else]) and not self.done():
|
|
self.expect(TokenType.Case, "expecting at least one 'case' label in switch statement")
|
|
match = self.expression()
|
|
self.expect(TokenType.LeftBrace, "expecting '{' after expression match in switch statement")
|
|
body = BlockStmt(self.blockStmt())
|
|
branches.add((cond: match, body: body))
|
|
if self.match(Else):
|
|
self.expect(TokenType.LeftBrace, "expecting '{' after else clause in switch statement")
|
|
default = BlockStmt(self.blockStmt())
|
|
self.expect(TokenType.RightBrace, "missing closing '}' in switch statement")
|
|
result = newSwitchStmt(switch, branches, default, tok)
|
|
|
|
|
|
proc statement(self: Parser): Statement =
|
|
## Parses statements
|
|
case self.peek().kind:
|
|
of TokenType.If:
|
|
discard self.step()
|
|
result = self.ifStmt()
|
|
of TokenType.Switch:
|
|
discard self.step()
|
|
result = self.switchStmt()
|
|
of TokenType.Assert:
|
|
discard self.step()
|
|
result = self.assertStmt()
|
|
of TokenType.Break:
|
|
discard self.step()
|
|
result = self.breakStmt()
|
|
of TokenType.Continue:
|
|
discard self.step()
|
|
result = self.continueStmt()
|
|
of TokenType.Return:
|
|
discard self.step()
|
|
result = self.returnStmt()
|
|
of TokenType.Import:
|
|
discard self.step()
|
|
result = self.importStmt()
|
|
of TokenType.Export:
|
|
discard self.step()
|
|
result = self.exportStmt()
|
|
of TokenType.While:
|
|
discard self.step()
|
|
result = self.whileStmt()
|
|
of TokenType.Foreach:
|
|
discard self.step()
|
|
result = self.forEachStmt()
|
|
of TokenType.LeftBrace:
|
|
discard self.step()
|
|
result = self.blockStmt()
|
|
of TokenType.Block:
|
|
discard self.step()
|
|
result = self.namedBlockStmt()
|
|
else:
|
|
result = self.expressionStatement()
|
|
result.file = self.file
|
|
|
|
|
|
proc parseTypeFields(self: Parser): TypeFields =
|
|
## Parses type fields
|
|
result = newOrderedTable[string, TypeField]()
|
|
var
|
|
argName: IdentExpr
|
|
argPrivate: bool
|
|
argType: Expression
|
|
argDefault: Expression
|
|
while not self.check(RightBrace) and not self.done():
|
|
self.expect(Identifier, "expecting type member name")
|
|
argName = newIdentExpr(self.peek(-1))
|
|
argPrivate = not self.match("*")
|
|
self.expect(":", "expecting ':' after type member name")
|
|
argType = self.expression()
|
|
if self.match("="):
|
|
argDefault = self.expression()
|
|
result[argName.token.lexeme] = TypeField(ident: argName, valueType: argType, default: argDefault, isPrivate: argPrivate)
|
|
if not self.check([";", "}"]):
|
|
if self.peek().kind == Semicolon:
|
|
discard self.step()
|
|
break
|
|
self.error("expecting semicolon or '}' after type member declaration")
|
|
self.expect(Semicolon, "missing semicolon after type member")
|
|
|
|
|
|
proc typeDecl(self: Parser): TypeDecl =
|
|
## Parses type declarations
|
|
let token = self.peek(-1)
|
|
self.expect(Identifier, "expecting type name after 'type'")
|
|
var name = newIdentExpr(self.peek(-1))
|
|
result = newTypeDecl(name, newOrderedTable[string, TypeField](), true, token, @[], nil, false, false)
|
|
result.file = self.file
|
|
if self.check(["<", "["]):
|
|
self.parseGenerics(result)
|
|
result.isPrivate = not self.match("*")
|
|
self.checkDecl(result.isPrivate)
|
|
self.expect("=", "expecting '=' after type name")
|
|
var hasNone = false
|
|
case self.peek().kind:
|
|
of Enum:
|
|
discard self.step()
|
|
result.isEnum = true
|
|
of Object:
|
|
discard self.step()
|
|
else:
|
|
# This is to allow using the ref keyword both to declare a simple
|
|
# type alias (i.e. type x = ref y) and to declare referenced structs
|
|
# (i.e. type x = ref object {,,,})
|
|
if self.check(TokenType.Ref) and self.check(TokenType.Object, 1):
|
|
discard self.step()
|
|
discard self.step()
|
|
result.isRef = true
|
|
else:
|
|
result.value = self.expression()
|
|
while not self.check(";") and not self.done():
|
|
case self.peek().lexeme:
|
|
of "|": # Untagged type unions
|
|
result.value = newBinaryExpr(result.value, self.step(), self.expression())
|
|
result.file = self.file
|
|
of "~":
|
|
result.value = newUnaryExpr(self.step(), result.value)
|
|
result.file = self.file
|
|
else:
|
|
self.error("invalid syntax")
|
|
if not result.isEnum and self.match("of"):
|
|
# Type has a parent (and is not an enumeration)
|
|
result.parent = self.expression()
|
|
if not self.match(";"):
|
|
self.expect(LeftBrace, "expecting '{' after type declaration")
|
|
if self.match(TokenType.Pragma):
|
|
for pragma in self.parsePragmas():
|
|
result.pragmas.add(pragma)
|
|
if not result.isEnum:
|
|
result.fields = self.parseTypeFields()
|
|
else:
|
|
var variant: TypeDecl
|
|
while not self.done():
|
|
variant = newTypeDecl(nil, nil, true, nil, @[], nil, false, false)
|
|
self.expect(Identifier, "expecting variant name")
|
|
variant.name = newIdentExpr(self.peek(-1))
|
|
variant.token = variant.name.token
|
|
if self.check(["[", "<"]):
|
|
self.parseGenerics(variant)
|
|
if self.match("{"):
|
|
variant.fields = self.parseTypeFields()
|
|
result.members.add(variant)
|
|
if self.match(","):
|
|
continue
|
|
elif self.check("}"):
|
|
break
|
|
self.expect(RightBrace, "expecting '}' after type declaration")
|
|
|
|
|
|
|
|
proc declaration(self: Parser): Declaration =
|
|
## Parses declarations
|
|
case self.peek().kind:
|
|
of TokenType.Var, TokenType.Const, TokenType.Let:
|
|
discard self.step()
|
|
result = self.varDecl()
|
|
of TokenType.Function:
|
|
discard self.step()
|
|
result = self.funDecl()
|
|
of TokenType.Operator:
|
|
discard self.step()
|
|
result = self.funDecl(isOperator=true)
|
|
of TokenType.Pragma:
|
|
discard self.step()
|
|
for p in self.parsePragmas():
|
|
self.tree.add(p)
|
|
of TokenType.Type:
|
|
discard self.step()
|
|
result = self.typeDecl()
|
|
of TokenType.Comment:
|
|
discard self.step() # TODO: Docstrings and stuff
|
|
else:
|
|
self.error(&"unknown token type {self.peek().kind} at declaration()")
|
|
|
|
|
|
proc dispatch(self: Parser): ASTNode =
|
|
case self.peek().kind:
|
|
of TokenType.Var, TokenType.Const, TokenType.Let, TokenType.Function,
|
|
TokenType.Operator, TokenType.Pragma, TokenType.Type:
|
|
return self.declaration()
|
|
of TokenType.Comment:
|
|
discard self.step() # TODO
|
|
else:
|
|
result = self.statement()
|
|
|
|
|
|
proc findOperators(self: Parser, tokens: seq[Token]) =
|
|
## Finds operators in a token stream
|
|
for i, token in tokens:
|
|
# We do a first pass over the tokens
|
|
# to find operators. Note that this
|
|
# relies on the lexer ending the input
|
|
# with an EOF token
|
|
if i == tokens.high() and token.kind != EndOfFile:
|
|
# Since we're iterating this list anyway we might as
|
|
# well perform some extra checks
|
|
self.error("invalid state: found malformed tokenizer input while looking for operators (missing EOF)", token)
|
|
elif token.kind == Operator:
|
|
self.operators.addOperator(tokens[i + 1].lexeme)
|
|
|
|
|
|
|
|
proc parse*(self: Parser, tokens: seq[Token], file: string, lines: seq[tuple[start, stop: int]], source: string, persist: bool = false): seq[ASTNode] =
|
|
## Parses a sequence of tokens into a sequence of AST nodes
|
|
|
|
# I'm way too lazy to figure out a better way to ignore
|
|
# comments, so here ya go
|
|
self.tokens = tokens.filterIt(it.kind != Comment)
|
|
self.file = file
|
|
self.source = source
|
|
self.lines = lines
|
|
self.current = 0
|
|
self.scopeDepth = 0
|
|
self.loopDepth = 0
|
|
self.tree = @[]
|
|
if not persist:
|
|
self.operators = newOperatorTable()
|
|
self.modules = newTable[string, bool]()
|
|
self.findOperators(tokens)
|
|
var node: ASTNode
|
|
while not self.done():
|
|
node = self.dispatch()
|
|
if not node.isNil():
|
|
# This only happens because we haven't implemented
|
|
# all of our grammar yet. Will be removed soon(TM)
|
|
self.tree.add(node)
|
|
result = self.tree |