Experimental support for multiple arguments of the same type using only one type declaration + various compiler fixes

This commit is contained in:
Mattia Giambirtone 2022-05-01 13:07:50 +02:00
parent 776968bffc
commit f2b60bd9c4
6 changed files with 177 additions and 108 deletions

View File

@ -33,20 +33,28 @@ export multibyte
type
NameKind = enum
Function, Type, Var
Name = ref object
## A compile-time wrapper around
## statically resolved names.
## Depth indicates to which scope
## the variable belongs, zero meaning
## the global one
name: IdentExpr
owner: string
depth: int
isPrivate: bool
isConst: bool
isLet: bool
valueType: IdentExpr
codePos: int
kind: NameKind
name: IdentExpr # Name of the identifier
owner: string # Owner of the identifier
depth: int # Scope depth
isPrivate: bool # Is this name private?
isConst: bool # Is this a constant?
isLet: bool # Can this name's value be mutated?
valueType: Expression # Name's type
codePos: int # The position in the bytecode
# where this name's StoreVar
# instruction was emitted. This
# is kept so that once we detect
# this name as a closed-over variable
# we can change the StoreVar into a StoreHeap
Loop = object
## A "loop object" used
@ -54,9 +62,9 @@ type
## appropriate jump offsets
## for continue and break
## statements
start: int
depth: int
breakPos: seq[int]
start: int # Position in the bytecode where the loop starts
depth: int # Scope depth where the loop is located
breakPos: seq[int] # List of positions where
Compiler* = ref object
## A wrapper around the compiler's state
@ -133,7 +141,6 @@ proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >=
## Utility functions
proc peek(self: Compiler, distance: int = 0): ASTNode =
## Peeks at the AST node at the given distance.
## If the distance is out of bounds, the last
@ -225,15 +232,15 @@ proc emitJump(self: Compiler, opcode: OpCode): int =
proc patchJump(self: Compiler, offset: int) =
## Patches a previously emitted jump
## using emitJump. Since emitJump assumes
## Patches a previously emitted relative
## jump using emitJump. Since emitJump assumes
## a long jump, this also shrinks the jump
## offset and changes the bytecode instruction if possible
## (i.e. jump is in 16 bit range), but the converse is also
## true (i.e. it might change a regular jump into a long one)
let jump: int = self.chunk.code.len() - offset
if jump > 16777215:
self.error("cannot jump more than 16777215 bytecode instructions")
self.error("cannot jump more than 16777216 bytecode instructions")
if jump < uint16.high().int:
case OpCode(self.chunk.code[offset]):
of LongJumpForwards:
@ -248,7 +255,7 @@ proc patchJump(self: Compiler, offset: int) =
self.chunk.code[offset] = JumpIfFalseOrPop.uint8()
else:
discard
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
self.chunk.code.delete(offset + 1) # Discards the first 8 bits of the jump offset (which are empty)
let offsetArray = (jump - 1).toDouble() # -1 since we got rid of 1 byte!
self.chunk.code[offset + 1] = offsetArray[0]
self.chunk.code[offset + 2] = offsetArray[1]
@ -290,9 +297,6 @@ proc literal(self: Compiler, node: ASTNode) =
self.emitByte(OpCode.Nan)
of strExpr:
self.emitConstant(node)
# The optimizer will emit warning
# for overflowing numbers. Here, we
# treat them as errors
of intExpr:
var x: int
var y = IntExpr(node)
@ -301,11 +305,6 @@ proc literal(self: Compiler, node: ASTNode) =
except ValueError:
self.error("integer value out of range")
self.emitConstant(y)
# Even though most likely the optimizer
# will collapse all these other literals
# to nodes of kind intExpr, that can be
# disabled. This also allows us to catch
# basic overflow errors before running any code
of hexExpr:
var x: int
var y = HexExpr(node)
@ -347,7 +346,7 @@ proc literal(self: Compiler, node: ASTNode) =
of listExpr:
var y = ListExpr(node)
if y.members.len() > 16777216:
self.error("collection literals can't have more than 16777216 elements")
self.error("list literals can't have more than 16777216 elements")
for member in y.members:
self.expression(member)
self.emitByte(BuildList)
@ -355,7 +354,7 @@ proc literal(self: Compiler, node: ASTNode) =
of tupleExpr:
var y = TupleExpr(node)
if y.members.len() > 16777216:
self.error("collection literals can't have more than 16777216 elements")
self.error("tuple literals can't have more than 16777216 elements")
for member in y.members:
self.expression(member)
self.emitByte(BuildTuple)
@ -363,7 +362,7 @@ proc literal(self: Compiler, node: ASTNode) =
of setExpr:
var y = SetExpr(node)
if y.members.len() > 16777216:
self.error("collection literals can't have more than 16777216 elements")
self.error("set literals can't have more than 16777216 elements")
for member in y.members:
self.expression(member)
self.emitByte(BuildSet)
@ -371,7 +370,7 @@ proc literal(self: Compiler, node: ASTNode) =
of dictExpr:
var y = DictExpr(node)
if y.keys.len() > 16777216:
self.error("collection literals can't have more than 16777216 elements")
self.error("dict literals can't have more than 16777216 elements")
for (key, value) in zip(y.keys, y.values):
self.expression(key)
self.expression(value)
@ -382,7 +381,7 @@ proc literal(self: Compiler, node: ASTNode) =
self.expression(y.expression)
self.emitByte(OpCode.Await)
else:
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug)")
self.error(&"invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!)")
proc unary(self: Compiler, node: UnaryExpr) =
@ -399,59 +398,84 @@ proc unary(self: Compiler, node: UnaryExpr) =
of Tilde:
self.emitByte(NoOp)
else:
self.error(&"invalid AST node of kind {node.kind} at unary(): {node} (This is an internal error and most likely a bug)")
self.error(&"invalid AST node of kind {node.kind} at unary(): {node} (This is an internal error and most likely a bug!)")
proc binary(self: Compiler, node: BinaryExpr) =
## Compiles all binary expressions
# These two lines prepare the stack by pushing the
# opcode's operands onto it
self.expression(node.a)
self.expression(node.b)
# TODO: Find implementation of
# the given operator and call it
case node.operator.kind:
of Plus:
# a + b
self.emitByte(NoOp)
of Minus:
# a - b
self.emitByte(NoOp)
of Star:
# a * b
self.emitByte(NoOp)
of DoubleStar:
# a ** b
self.emitByte(NoOp)
of Percentage:
# a % b
self.emitByte(NoOp)
of FloorDiv:
# a // b
self.emitByte(NoOp)
of Slash:
# a / b
self.emitByte(NoOp)
of Ampersand:
# a & b
self.emitByte(NoOp)
of Caret:
# a ^ b
self.emitByte(NoOp)
of Pipe:
self.emitByte(NoOp)
of As:
# a | b
self.emitByte(NoOp)
of Is:
# a is b
self.emitByte(NoOp)
of IsNot:
# a isnot b
self.emitByte(NoOp)
of Of:
# a of b
self.emitByte(NoOp)
of As:
# a as b
self.emitByte(NoOp)
of RightShift:
# a >> b
self.emitByte(NoOp)
of LeftShift:
# a << b
self.emitByte(NoOp)
of LessThan:
# a < b
self.emitByte(NoOp)
of GreaterThan:
# a > b
self.emitByte(NoOp)
of DoubleEqual:
# a == b
self.emitByte(NoOp)
of LessOrEqual:
# a <= b
self.emitByte(NoOp)
of GreaterOrEqual:
# a >= b
self.emitByte(NoOp)
of LogicalAnd:
# a and b
self.expression(node.a)
var jump: int
if self.enableOptimizations:
@ -462,17 +486,16 @@ proc binary(self: Compiler, node: BinaryExpr) =
self.expression(node.b)
self.patchJump(jump)
of LogicalOr:
# a or b
self.expression(node.a)
let jump = self.emitJump(JumpIfTrue)
self.expression(node.b)
self.patchJump(jump)
# TODO: In-place operations
else:
self.error(&"invalid AST node of kind {node.kind} at binary(): {node} (This is an internal error and most likely a bug)")
self.error(&"invalid AST node of kind {node.kind} at binary(): {node} (This is an internal error and most likely a bug!)")
proc declareName(self: Compiler, node: Declaration, kind: IdentExpr) =
proc declareName(self: Compiler, node: Declaration, kind: Expression) =
## Statically declares a name into the current scope
case node.kind:
of NodeKind.varDecl:
@ -489,28 +512,26 @@ proc declareName(self: Compiler, node: Declaration, kind: IdentExpr) =
isConst: node.isConst,
valueType: kind,
codePos: self.chunk.code.len(),
isLet: node.isLet))
isLet: node.isLet,
kind: Var))
self.emitByte(StoreVar)
self.emitBytes(self.names.high().toTriple())
of funDecl:
of NodeKind.funDecl:
var node = FunDecl(node)
# Declares the function's name in the
# current (outer) scope...
# current scope but no StoreVar is emitted
# because a function's name is only useful
# at compile time and has no runtime meaning
# given that we implement functions with jumps
self.names.add(Name(depth: self.scopeDepth,
isPrivate: node.isPrivate,
isConst: false,
owner: self.currentModule,
valueType: node.returnType,
codePos: self.chunk.code.len(),
codePos: -1,
name: node.name,
isLet: false))
self.emitByte(StoreVar)
self.emitBytes(self.names.high().toTriple())
# ... but its arguments in an inner one!
self.scopeDepth += 1
# (this ugly part is needed because
# self.blockStmt() already increments
# and decrements the scope depth)
isLet: false,
kind: Function))
for argument in node.arguments:
if self.names.high() > 16777215:
self.error("cannot declare more than 16777216 variables at a time")
@ -521,10 +542,10 @@ proc declareName(self: Compiler, node: Declaration, kind: IdentExpr) =
name: argument.name,
valueType: kind,
codePos: self.chunk.code.len(),
isLet: false))
isLet: false,
kind: Var))
self.emitByte(StoreVar)
self.emitBytes(self.names.high().toTriple())
self.scopeDepth -= 1
# TODO: Default arguments and unpacking
else:
discard # Unreachable
@ -608,6 +629,7 @@ proc detectClosureVariable(self: Compiler, name: IdentExpr, depth: int = self.sc
proc identifier(self: Compiler, node: IdentExpr) =
## Compiles access to identifiers
let s = self.resolve(node)
echo s[]
if s == nil:
self.error(&"reference to undeclared name '{node.token.lexeme}'")
elif s.isConst:
@ -641,8 +663,7 @@ proc assignment(self: Compiler, node: ASTNode) =
case node.kind:
of assignExpr:
var node = AssignExpr(node)
# TODO: This will explode with slicing!
var name = IdentExpr(node.name)
let name = IdentExpr(node.name)
let r = self.resolve(name)
if r == nil:
self.error(&"assignment to undeclared name '{node.name}'")
@ -1116,7 +1137,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
# are resolved properly). There's a need for a bit
# of boilerplate code to make closures work, but
# that's about it
self.emitBytes(OpCode.Nil, OpCode.Return)
# Currently defer is not functional so we

View File

@ -184,11 +184,11 @@ type
LambdaExpr* = ref object of Expression
body*: Statement
arguments*: seq[tuple[name: IdentExpr, valueType: IdentExpr]]
arguments*: seq[tuple[name: IdentExpr, valueType: Expression]]
defaults*: seq[Expression]
isGenerator*: bool
isAsync*: bool
returnType*: IdentExpr
returnType*: Expression
SliceExpr* = ref object of Expression
expression*: Expression
@ -258,17 +258,17 @@ type
isConst*: bool
isPrivate*: bool
isLet*: bool
valueType*: IdentExpr
valueType*: Expression
FunDecl* = ref object of Declaration
name*: IdentExpr
body*: Statement
arguments*: seq[tuple[name: IdentExpr, valueType: IdentExpr]]
arguments*: seq[tuple[name: IdentExpr, valueType: Expression]]
defaults*: seq[Expression]
isAsync*: bool
isGenerator*: bool
isPrivate*: bool
returnType*: IdentExpr
returnType*: Expression
proc newASTNode*(kind: NodeKind, token: Token): ASTNode =
@ -371,8 +371,8 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
result.token = token
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: IdentExpr]], defaults: seq[Expression], body: Statement,
isGenerator: bool, isAsync: bool, token: Token): LambdaExpr =
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression], body: Statement,
isGenerator: bool, isAsync: bool, token: Token, returnType: Expression): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr)
result.body = body
result.arguments = arguments
@ -380,6 +380,7 @@ proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: IdentExpr]]
result.isGenerator = isGenerator
result.isAsync = isAsync
result.token = token
result.returnType = returnType
proc newGetItemExpr*(obj: Expression, name: IdentExpr, token: Token): GetItemExpr =
@ -578,7 +579,7 @@ proc newIfStmt*(condition: Expression, thenBranch, elseBranch: Statement,
proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false,
isPrivate: bool = true, token: Token, isLet: bool = false,
valueType: IdentExpr, pragmas: seq[Token]): VarDecl =
valueType: Expression, pragmas: seq[Token]): VarDecl =
result = VarDecl(kind: varDecl)
result.name = name
result.value = value
@ -590,9 +591,10 @@ proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false,
result.pragmas = pragmas
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: IdentExpr]], defaults: seq[Expression],
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
isPrivate: bool, token: Token, pragmas: seq[Token]): FunDecl =
isPrivate: bool, token: Token, pragmas: seq[Token],
returnType: Expression): FunDecl =
result = FunDecl(kind: funDecl)
result.name = name
result.arguments = arguments
@ -603,6 +605,7 @@ proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueTyp
result.isPrivate = isPrivate
result.token = token
result.pragmas = pragmas
result.returnType = returnType

View File

@ -50,11 +50,17 @@ type
OpCode* {.pure.} = enum
## Enum of possible opcodes.
# Note: x represents the
# argument to unary opcodes, while
# a and b represent arguments to binary
# opcodes. Other variable names (c, d, etc.)
# may be used for more complex opcodes
# Note: x represents the argument
# to unary opcodes, while a and b
# represent arguments to binary
# opcodes. Other variable names (c, d, ...)
# may be used for more complex opcodes. If
# an opcode takes any arguments at runtime,
# they come from either the stack or the VM's
# closure array. Some other opcodes (e.g.
# jumps), take arguments in the form of 16
# or 24 bit numbers that are defined statically
# at compilation time into the bytecode.
LoadConstant = 0u8, # Pushes constant at position x in the constant table onto the stack
## Constant opcodes (each of them pushes a constant singleton on the stack)
@ -68,19 +74,19 @@ type
Push, # Pushes x onto the stack
PopN, # Pops x elements off the stack (optimization for exiting scopes and returning from functions)
## Name resolution/handling
LoadAttribute,
LoadVar, # Pushes the object at position x in the stack
LoadAttribute, # Pushes the attribute b of object a onto the stack
LoadVar, # Pushes the object at position x in the stack onto the stack
StoreVar, # Stores the value of b at position a in the stack
LoadHeap, # Pushes the object position x in the closure array
LoadHeap, # Pushes the object position x in the closure array onto the stack
StoreHeap, # Stores the value of b at position a in the closure array
## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode at offset x
JumpIfFalse, # Jumps to an absolute index (x) in the bytecode if the value at the top of the stack is falsey
JumpIfTrue, # Jumps to an absolute index (x) in the bytecode if the value at the top of the stack is truthy
JumpIfFalsePop, # Like JumpIfFalse, but it also pops off the stack (regardless of truthyness). Optimization for if statements
JumpIfFalseOrPop, # Jumps to an absolute index (x) in the bytecode if the value at the top of the stack is falsey and pops it otherwise
JumpForwards, # Relative, unconditional, positive jump of size x in the bytecode
JumpBackwards, # Relative, unconditional, negative jump of size x in the bytecode
Jump, # Absolute, unconditional jump into the bytecode
JumpForwards, # Relative, unconditional, positive jump in the bytecode
JumpBackwards, # Relative, unconditional, negative jump in the bytecode
JumpIfFalse, # Jumps to an absolute index in the bytecode if x is true
JumpIfTrue, # Jumps to an absolute index in the bytecode if x is false
JumpIfFalsePop, # Like JumpIfFalse, but also pops off the stack (regardless of truthyness). Optimization for if statements
JumpIfFalseOrPop, # Jumps to an absolute index in the bytecode if x is false and pops it otherwise (used for logical and)
## Long variants of jumps (they use a 24-bit operand instead of a 16-bit one)
LongJump,
LongJumpIfFalse,
@ -90,25 +96,22 @@ type
LongJumpForwards,
LongJumpBackwards,
## Functions
Call, # Calls a function
Return # Returns from the current function
## Exception handling
Raise, # Raises exception x or re-raises active exception if x is nil
BeginTry, # Initiates an exception handling context
FinishTry, # Closes the current exception handling context
## Generators
Yield,
Yield, # Yields control from a generator back to the caller
## Coroutines
Await,
Await, # Calls an asynchronous function
## Collection literals
BuildList,
BuildDict,
BuildSet,
BuildTuple,
## Misc
Assert, # Raises an AssertionFailed exception if the value at the top of the stack is falsey
Slice, # Slices an object (takes 3 arguments: start, stop, step). Pushes the result of a.subscript(b, c, d) onto the stack
GetItem, # Pushes the result of a.getItem(b) onto the stack
Assert, # Raises an AssertionFailed exception if the x is false
NoOp, # Just a no-op
@ -120,14 +123,15 @@ const simpleInstructions* = {OpCode.Return, OpCode.Nil,
OpCode.Nan, OpCode.Inf,
Pop, OpCode.Raise,
BeginTry, FinishTry,
OpCode.Yield, OpCode.Await}
OpCode.Yield, OpCode.Await,
OpCode.NoOp}
# Constant instructions are instructions that operate on the bytecode constant table
const constantInstructions* = {LoadConstant, }
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers
const stackTripleInstructions* = {Call, StoreVar, LoadVar, LoadHeap, StoreHeap}
const stackTripleInstructions* = {StoreVar, LoadVar, LoadHeap, StoreHeap}
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers

View File

@ -36,10 +36,10 @@ type
Var, Let, Const, Is, Return,
Coroutine, Generator, Import,
IsNot, Raise, Assert, Await,
Foreach, Yield, Public, As,
Of, Defer, Try, Except, Finally,
Type, Operator, Case, Enum, From,
Emit
Foreach, Yield, Of, Defer,
Try, Except, Finally, Type,
Operator, Case, Enum, From,
Emit, As
# Literal types
Integer, Float, String, Identifier,

View File

@ -445,7 +445,7 @@ proc comparison(self: Parser): Expression =
result = self.add()
var operator: Token
var right: Expression
while self.match([LessThan, GreaterThan, LessOrEqual, GreaterOrEqual, Is, As, Of, IsNot]):
while self.match([LessThan, GreaterThan, LessOrEqual, GreaterOrEqual, Is, Of, IsNot]):
operator = self.peek(-1)
right = self.add()
result = newBinaryExpr(result, operator, right)
@ -863,9 +863,9 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
## Parses functions, coroutines, generators, anonymous functions and custom operators
let tok = self.peek(-1)
var enclosingFunction = self.currentFunction
var arguments: seq[tuple[name: IdentExpr, valueType: IdentExpr]] = @[]
var arguments: seq[tuple[name: IdentExpr, valueType: Expression]] = @[]
var defaults: seq[Expression] = @[]
var returnType: IdentExpr
var returnType: Expression
if not isLambda and self.check(Identifier):
# We do this extra check because we might
# be called from a context where it's
@ -877,7 +877,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
self.checkDecl(not self.check(Star))
self.currentFunction = newFunDecl(nil, arguments, defaults, newBlockStmt(@[], Token()),
isAsync=isAsync, isGenerator=isGenerator, isPrivate=true,
token=tok, pragmas=(@[]))
token=tok, pragmas=(@[]), returnType=nil)
FunDecl(self.currentFunction).name = newIdentExpr(self.peek(-1))
if self.match(Star):
FunDecl(self.currentFunction).isPrivate = false
@ -894,26 +894,69 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
self.currentFunction = enclosingFunction
return result
elif isLambda:
self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator=isGenerator, isAsync=isAsync, token=tok)
self.currentFunction = newLambdaExpr(arguments, defaults, newBlockStmt(@[], Token()), isGenerator=isGenerator, isAsync=isAsync, token=tok,
returnType=nil)
elif not isOperator:
self.error("funDecl: invalid state")
if self.match(Colon):
# A function without an explicit
# return type is the same as a void
# function in C (i.e. no return type)
self.expect([Identifier, Nil], "expecting function return type after ':'")
returnType = newIdentExpr(self.peek(-1))
# Function has explicit return type
if self.match([Function, Coroutine, Generator]):
# The function's return type is another
# function or callable object
var arguments: seq[tuple[name: IdentExpr, valueType: Expression]] = @[]
var defaults: seq[Expression] = @[]
returnType = newLambdaExpr(arguments, defaults, nil, isGenerator=self.peek(-1).kind == Generator,
isAsync=self.peek(-1).kind == Coroutine,
token=self.peek(-1), returnType=nil)
var parameter: tuple[name: IdentExpr, valueType: Expression]
if self.match(LeftParen):
var hasType = false
var j = 0
while not self.check(RightParen):
if arguments.len > 255:
self.error("cannot have more than 255 arguments in function declaration")
self.expect(Identifier, "expecting parameter name")
parameter.name = newIdentExpr(self.peek(-1))
if self.match(Colon):
hasType = true
parameter.valueType = self.expression()
for i in j..arguments.high():
arguments[i].valueType = parameter.valueType
else:
hasType = false
j = arguments.high()
if parameter in arguments:
self.error("duplicate parameter name in function declaration")
arguments.add(parameter)
if self.match(Equal):
defaults.add(self.expression())
elif defaults.len() > 0:
self.error("positional argument cannot follow default argument in function declaration")
if not self.match(Comma):
break
self.expect(RightParen)
if not hasType:
self.error("cannot omit type declaration for function parameters")
if self.match(Colon):
LambdaExpr(returnType).returnType = self.expression()
if not self.match(LeftBrace):
var parameter: tuple[name: IdentExpr, valueType: IdentExpr]
var parameter: tuple[name: IdentExpr, valueType: Expression]
self.expect(LeftParen)
while not self.check(RightParen):
var hasType = false
var j = 0
if arguments.len > 255:
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 self.match(Colon):
hasType = true
parameter.valueType = self.expression()
for i in j..arguments.high():
arguments[i].valueType = parameter.valueType
else:
hasType = false
j = arguments.high()
if parameter in arguments:
self.error("duplicate parameter name in function declaration")
arguments.add(parameter)
@ -926,8 +969,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL
self.expect(RightParen)
if self.match(Colon):
# Function's return type
self.expect(Identifier, "expecting return type after ':'")
returnType = newIdentExpr(self.peek(-1))
returnType = self.expression()
self.expect(LeftBrace)
if self.currentFunction.kind == funDecl:
if not self.match(Semicolon):

View File

@ -29,7 +29,7 @@ proc getLineEditor: LineEditor
# Handy dandy compile-time constants
const debugLexer = false
const debugParser = false
const debugParser = true
const debugCompiler = true
const debugOptimizer = false
const debugSerializer = true
@ -264,7 +264,7 @@ proc fillSymbolTable(tokenizer: Lexer) =
# and specialize them later
tokenizer.symbols.addKeyword("isnot", IsNot)
tokenizer.symbols.addKeyword("is", Is)
tokenizer.symbols.addKeyword("as", As)
tokenizer.symbols.addKeyword("is", As)
tokenizer.symbols.addKeyword("of", Of)
tokenizer.symbols.addKeyword("and", TokenType.LogicalAnd)
tokenizer.symbols.addKeyword("or", TokenType.LogicalOr)