nondescript/src/ndspkg/compiler/functions.nim

128 lines
3.6 KiB
Nim

{.used.}
import strformat
import ../scanner
import ../chunk
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
# create the call env
# current stack before opCall:
# ... <funct obj> <arg1> <arg2> <arg3>
# opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3>
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 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()
when assertionsCompiler:
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)
tkFunct.genRule(parseFunct, nop, pcNone)