Experimental support for arbitrary user-defined operators

This commit is contained in:
Nocturn9x 2022-04-06 20:08:36 +02:00
parent 6500dd632e
commit 2e4091de1f
3 changed files with 62 additions and 4 deletions

View File

@ -64,6 +64,9 @@ proc addSymbol*(self: SymbolTable, lexeme: string, token: TokenType) =
self.symbols[lexeme] = token
proc existsSymbol*(self: SymbolTable, lexeme: string): bool {.inline.} = lexeme in self.symbols
proc removeSymbol*(self: SymbolTable, lexeme: string) =
## Removes a symbol from the symbol table
## (does nothing if it does not exist)

View File

@ -62,6 +62,8 @@ type
currentFunction: ASTNode
# Stores the current scope depth (0 = global, > 0 local)
scopeDepth: int
# We store user-defined operators for later use
operators: seq[string]
proc newParser*(): Parser =
@ -384,14 +386,23 @@ proc unary(self: Parser): ASTNode =
result = self.call()
proc customUnaryOperator(self: Parser): ASTNode =
## Parses user-defined unary expressions
if self.peek().lexeme in self.operators:
discard self.step()
result = newUnaryExpr(self.peek(-1), self.customUnaryOperator())
else:
result = self.unary()
proc pow(self: Parser): ASTNode =
## Parses exponentiation expressions
result = self.unary()
result = self.customUnaryOperator()
var operator: Token
var right: ASTNode
while self.match(DoubleStar):
operator = self.peek(-1)
right = self.unary()
right = self.customUnaryOperator()
result = newBinaryExpr(result, operator, right)
@ -486,11 +497,22 @@ proc bitwiseOr(self: Parser): ASTNode =
result = newBinaryExpr(result, operator, right)
proc customBinaryOperator(self: Parser): ASTNode =
## Parses user-defined binary operators
result = self.bitwiseOr()
var operator: Token
var right: ASTNode
while self.peek().lexeme in self.operators:
operator = self.step()
right = self.bitwiseOr()
result = newBinaryExpr(result, operator, right)
proc assignment(self: Parser): ASTNode =
## Parses assignment, the highest-level
## expression (including stuff like a.b = 1).
## Slice assignments are also parsed here
result = self.bitwiseOr()
result = self.customBinaryOperator()
if self.match([Equal, InplaceAdd, InplaceSub, InplaceDiv, InplaceMod,
InplacePow, InplaceMul, InplaceXor, InplaceAnd, InplaceOr,
InplaceFloorDiv, InplaceRightShift, InplaceLeftShift]):
@ -880,6 +902,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
self.error("cannot have more than 255 arguments in function declaration")
self.expect(Identifier, "expecting parameter name")
parameter.name = newIdentExpr(self.peek(-1))
self.expect(Colon, "expecting ':' after parameter name")
self.expect(Identifier, "expecting parameter type")
parameter.valueType = newIdentExpr(self.peek(-1))
if parameter in arguments:
@ -892,6 +915,10 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
if not self.match(Comma):
break
self.expect(RightParen)
if self.match(Colon):
# 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):
@ -905,6 +932,14 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
LambdaExpr(self.currentFunction).arguments = arguments
LambdaExpr(self.currentFunction).returnType = returnType
result = self.currentFunction
if isOperator:
# isOperator is only true for functions
# with a name (since nameless operators
# don't make much sense)
if arguments.len() == 0:
self.error("cannot declare argument-less operator")
elif arguments.len() > 2:
self.error("cannot declare operator with more than 2 arguments")
self.currentFunction = enclosingFunction
@ -993,6 +1028,9 @@ proc declaration(self: Parser): ASTNode =
of Generator:
discard self.step()
result = self.funDecl(isGenerator=true)
of Operator:
discard self.step()
result = self.funDecl(isOperator=true)
of Type, Comment, Whitespace, Tab:
discard self.step() # TODO
else:
@ -1007,5 +1045,20 @@ proc parse*(self: Parser, tokens: seq[Token], file: string): seq[ASTNode] =
self.currentLoop = None
self.currentFunction = nil
self.scopeDepth = 0
self.operators = @[]
for i, token in self.tokens:
# We do a first pass over the tokens
# to find user-defined operators.
# Note that this relies on the lexer
# 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")
elif self.tokens[i + 1].kind != Identifier:
# The input is probably malformed,
# we'll catch this error later
break
else:
self.operators.add(self.tokens[i + 1].lexeme)
while not self.done():
result.add(self.declaration())
result.add(self.declaration())

View File

@ -42,11 +42,13 @@ when isMainModule:
if input.len() > 0:
# Currently the parser doesn't handle these tokens well
tokens = filter(tokenizer.lex(input, "<stdin>"), proc (x: Token): bool = x.kind notin {Whitespace, Tab})
echo "Tokenization step:"
for i, token in tokens:
if i == tokens.high():
# Who cares about EOF?
break
echo token
echo "Parsing step:"
for node in parser.parse(tokens, "<stdin>"):
echo node
except IOError: