nondescript/src/ndspkg/compiler/functions.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)