149 lines
4.3 KiB
Nim
149 lines
4.3 KiB
Nim
{.used.}
|
|
|
|
import strformat
|
|
import bitops
|
|
|
|
import ../scanner
|
|
import ../chunk
|
|
import ../types/value
|
|
import ../config
|
|
|
|
import types
|
|
import utils
|
|
import precedence
|
|
import jumps
|
|
import scope
|
|
|
|
proc grouping(comp: Compiler) =
|
|
# assume initial '(' is already consumed
|
|
comp.expression()
|
|
comp.consume(tkRightParen, "Expect ')' after expression.")
|
|
|
|
proc parseArgs(comp: Compiler): int =
|
|
var argcount = 0
|
|
# put args on stack
|
|
while comp.current.tokenType notin {tkRightParen, tkEof}:
|
|
comp.expression()
|
|
inc argcount
|
|
if comp.current.tokenType != tkRightParen:
|
|
comp.consume(tkComma, "Expected ',' between arguments in function calls.")
|
|
comp.consume(tkRightParen, "Expected ')' after arguments in function calls.")
|
|
return argcount
|
|
|
|
|
|
proc parseCall(comp: Compiler) =
|
|
# ( consumed
|
|
|
|
let argcount = comp.parseArgs()
|
|
|
|
# emit call
|
|
comp.writeChunk(-argcount, opCall)
|
|
comp.writeChunk(0, argcount.uint8)
|
|
|
|
tkLeftParen.genRule(grouping, parseCall, pcCall)
|
|
|
|
proc parsePipeCall(comp: Compiler) =
|
|
# can be followed by:
|
|
# idents
|
|
# idents+indexes
|
|
# NOT calls, so the parsePrecedence after it should look for pcIndex at most
|
|
|
|
# get the function on the stack
|
|
comp.parsePrecedence(pcIndex)
|
|
|
|
# swap the function and the first arg
|
|
comp.writeChunk(0, opSwap)
|
|
|
|
var argcount = 1
|
|
if comp.match(tkLeftParen):
|
|
argcount = 1 + comp.parseArgs()
|
|
|
|
comp.writeChunk(-argcount, opCall)
|
|
comp.writeChunk(0, argcount.uint8)
|
|
|
|
tkDoublecolon.genRule(nop, parsePipeCall, pcAmpersand)
|
|
|
|
proc parseArrowCall(comp: Compiler) =
|
|
# -> syntactic sugar for table "methods"
|
|
|
|
# when called, the stack contains the table
|
|
# the table should be duplicated first
|
|
comp.writeChunk(1, opDup)
|
|
# then the index should be read
|
|
comp.consume(tkIdentifier, "Identifier (index) expected after '->'.")
|
|
let index = comp.previous.text.fromNimString()
|
|
comp.writeConstant(index)
|
|
comp.writeChunk(-1, opGetIndex)
|
|
# the stack's state now is table, method
|
|
# invert to get the right alignment for further function call
|
|
comp.writeChunk(0, opSwap)
|
|
# now the same code as :: applies
|
|
var argcount = 1
|
|
# args optional
|
|
if comp.match(tkLeftParen):
|
|
argcount = 1 + comp.parseArgs()
|
|
|
|
comp.writeChunk(-argcount, opCall)
|
|
comp.writeChunk(0, argcount.uint8)
|
|
|
|
|
|
tkArrow.genRule(nop, parseArrowCall, pcIndex)
|
|
|
|
proc parseFunct*(comp: Compiler) =
|
|
|
|
# jump over
|
|
var jumpStacklen = 0
|
|
let jumpOverBody = comp.emitJump(1, opFunctionDef, jumpStacklen)
|
|
|
|
comp.consume(tkLeftParen, "Expected '(' after keyword 'funct'.")
|
|
|
|
var params: seq[string]
|
|
# parameters
|
|
while comp.current.tokenType == tkIdentifier:
|
|
comp.advance()
|
|
params.add(comp.previous.text)
|
|
if comp.current.tokenType == tkRightParen:
|
|
break
|
|
comp.consume(tkComma, "Expected ',' to separate items in the parameter list.")
|
|
|
|
comp.consume(tkRightParen, "Expected ')' after parameter list.")
|
|
|
|
# function body:
|
|
let functII = comp.chunk.len
|
|
comp.beginScope(function = true) # this saves the old stackindex, sets it to 0, :function and :result at index 0
|
|
# assumption:
|
|
# the caller will create the following stack for the function to run in:
|
|
# [0] = return value placeholder
|
|
# [1] = arg #1
|
|
# [2] = arg #2
|
|
# [3] = arg #3
|
|
|
|
if params.len > shortArgMax:
|
|
comp.error("Too many parameters.")
|
|
|
|
comp.writeChunk(0, opCheckArity) # runtime arity check
|
|
comp.writeChunk(0, params.len.uint8)
|
|
|
|
for i in countup(1, params.len):
|
|
comp.stackIndex = i
|
|
comp.addLocal(params[i-1], 0)
|
|
comp.expression()
|
|
let shouldbeStackIndex = params.len + 1
|
|
if shouldbeStackIndex != comp.stackIndex:
|
|
comp.error(&"Assertion failed: wrong stackindex ({comp.stackIndex}) in function declaration (should be {shouldbeStackIndex}).")
|
|
let f = comp.endScope()
|
|
dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
|
|
|
|
# end of function declaration:
|
|
comp.patchJump(jumpOverBody, jumpStacklen)
|
|
# closures are implemented in a way, where the vm pops the function from the stack
|
|
# and reads the upvalue details from the following bytes
|
|
if f.upvalues.len() > 0:
|
|
comp.writeChunk(0, opClosure)
|
|
comp.writeChunk(0, f.upvalues.len().toDU8())
|
|
for upval in f.upvalues:
|
|
comp.writeChunk(0, upval.index.toDU8())
|
|
comp.writeChunk(0, if upval.isLocal: 0'u8 else: 1'u8)
|
|
|
|
tkProc.genRule(parseFunct, nop, pcNone)
|