split up expression.nim

This commit is contained in:
prod2 2022-02-08 05:50:36 +01:00
parent 960be5f0dd
commit d4a6921302
5 changed files with 277 additions and 234 deletions

View File

@ -0,0 +1,76 @@
import strformat
import strutils
import bitops # needed for value
import ../scanner
import ../chunk
import ../types/value
import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types
import utils
import precedence
import jumps
import scope
# lists
proc parseList(comp: Compiler) =
var count: int
while comp.current.tokenType != tkRightBracket:
comp.expression()
count.inc()
if comp.current.tokenType != tkRightBracket:
comp.consume(tkComma, "Comma expected after list member.")
comp.consume(tkRightBracket, "Right bracket expected after list members.")
if count > argMax:
comp.error("Maximum list length exceeded.")
comp.writeChunk(1 - count, opCreateList)
comp.writeChunk(0, count.toDU8())
tkStartList.genRule(parseList, nop, pcNone)
# tables
proc parseTable(comp: Compiler) =
var count: int
while comp.current.tokenType != tkRightBrace:
comp.expression()
comp.consume(tkEqual, "Equal sign expected after key.")
comp.expression()
count.inc()
if comp.current.tokenType != tkRightBrace:
comp.consume(tkComma, "Comma expected after key-value pair.")
comp.consume(tkRightBrace, "Right brace expected after table members.")
if count > argMax:
comp.error("Maximum table length exceeded.")
comp.writeChunk(1 - 2 * count, opCreateTable)
comp.writeChunk(0, count.toDU8())
tkStartTable.genRule(parseTable, nop, pcNone)
# len op
proc parseLen(comp: Compiler) =
comp.expression()
comp.writeChunk(0, opLen)
tkHashtag.genRule(parseLen, nop, pcNone)
# get/set index
proc parseIndex(comp: Compiler) =
# the index
comp.expression()
comp.consume(tkRightBracket, "Right bracket expected after index.")
if comp.match(tkEqual):
comp.parsePrecedence(pcNonAssignTop)
comp.writeChunk(-2, opSetIndex)
else:
comp.writeChunk(-1, opGetIndex)
tkLeftBracket.genRule(nop, parseIndex, pcIndex)

View File

@ -2,17 +2,15 @@ import ../scanner
import ../chunk
import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types
import utils
#import precedence
#import jumps
#import scope
#import variables
#import expressions
import statement
# the following modify precedence by being imported
import expressions
import controlflow
import collections
import functions
import statement
proc compile*(comp: Compiler) =
comp.scanner = newScanner(comp.source)

View File

@ -0,0 +1,92 @@
import strformat
import strutils
import bitops # needed for value
import ../scanner
import ../chunk
import ../types/value
import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types
import utils
import precedence
import jumps
import scope
proc ifExpr(comp: Compiler) =
# if expressions return the body if condition is truthy,
# the else expression otherwise, unless there is no else:
# if there is no else, it returns the condition if it is falsey
comp.consume(tkLeftParen, "Expect '(' after 'if'.")
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let thenJump = comp.emitJump(0, opJumpIfFalse)
# conditional code that can be jumped over must leave the stack in tact!
comp.writeChunk(-1, opPop)
comp.expression()
# net change to stack: -1 + 1 = 0
let elseJump = comp.emitJump(0, opJump)
comp.patchJump(thenJump)
if comp.match(tkElse):
comp.writeChunk(-1, opPop)
comp.expression()
comp.patchJump(elseJump)
tkIf.genRule(ifExpr, nop, pcNone)
proc andExpr(comp: Compiler) =
let endJump = comp.emitJump(0, opJumpIfFalse)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcAnd)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkAnd.genRule(nop, andExpr, pcAnd)
proc orExpr(comp: Compiler) =
let elseJump = comp.emitJump(0, opJumpIfFalse)
let endJump = comp.emitJump(0, opJump)
comp.patchJump(elseJump)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcOr)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkOr.genRule(nop, orExpr, pcOr)
proc parseWhile(comp: Compiler) =
comp.writeChunk(1, opNil) # return value
let loopStart = comp.chunk.len
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
# condition
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let exitJump = comp.emitJump(-1, opJumpIfFalsePop)
# this cannot be handled with just opPop, since the net change in the
# stack size inside code that is conditional must be 0!
# body
comp.writeChunk(-1, opPop) # pop the old return value
comp.expression()
# net stack change: 1 + -1 = 0
comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop)
comp.patchJump(exitJump)
tkWhile.genRule(parseWhile, nop, pcNone)

View File

@ -16,8 +16,6 @@ import utils
import precedence
import jumps
import scope
import variables
# EXPRESSIONS
@ -53,35 +51,6 @@ proc expString(comp: Compiler) =
tkString.genRule(expString, nop, pcNone)
proc grouping(comp: Compiler) =
# assume initial '(' is already consumed
comp.expression()
comp.consume(tkRightParen, "Expect ')' after expression.")
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>
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.")
# emit call
comp.writeChunk(-argcount, opCall)
comp.writeChunk(0, argcount.uint8)
tkLeftParen.genRule(grouping, parseCall, pcCall)
proc unary(comp: Compiler) =
let opType = comp.previous.tokenType
@ -142,201 +111,6 @@ tkGreaterEqual.genRule(nop, binary, pcComparison)
tkLess.genRule(nop, binary, pcComparison)
tkLessEqual.genRule(nop, binary, pcComparison)
proc ifExpr(comp: Compiler) =
# if expressions return the body if condition is truthy,
# the else expression otherwise, unless there is no else:
# if there is no else, it returns the condition if it is falsey
comp.consume(tkLeftParen, "Expect '(' after 'if'.")
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let thenJump = comp.emitJump(0, opJumpIfFalse)
# conditional code that can be jumped over must leave the stack in tact!
comp.writeChunk(-1, opPop)
comp.expression()
# net change to stack: -1 + 1 = 0
let elseJump = comp.emitJump(0, opJump)
comp.patchJump(thenJump)
if comp.match(tkElse):
comp.writeChunk(-1, opPop)
comp.expression()
comp.patchJump(elseJump)
tkIf.genRule(ifExpr, nop, pcNone)
proc andExpr(comp: Compiler) =
let endJump = comp.emitJump(0, opJumpIfFalse)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcAnd)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkAnd.genRule(nop, andExpr, pcAnd)
proc orExpr(comp: Compiler) =
let elseJump = comp.emitJump(0, opJumpIfFalse)
let endJump = comp.emitJump(0, opJump)
comp.patchJump(elseJump)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcOr)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkOr.genRule(nop, orExpr, pcOr)
proc parseWhile(comp: Compiler) =
comp.writeChunk(1, opNil) # return value
let loopStart = comp.chunk.len
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
# condition
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let exitJump = comp.emitJump(-1, opJumpIfFalsePop)
# this cannot be handled with just opPop, since the net change in the
# stack size inside code that is conditional must be 0!
# body
comp.writeChunk(-1, opPop) # pop the old return value
comp.expression()
# net stack change: 1 + -1 = 0
comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop)
comp.patchJump(exitJump)
tkWhile.genRule(parseWhile, nop, pcNone)
proc parseFunct(comp: Compiler) =
# jump over
let jumpOverBody = comp.emitJump(1, opFunctionDef)
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)
# 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)
# lists
proc parseList(comp: Compiler) =
var count: int
while comp.current.tokenType != tkRightBracket:
comp.expression()
count.inc()
if comp.current.tokenType != tkRightBracket:
comp.consume(tkComma, "Comma expected after list member.")
comp.consume(tkRightBracket, "Right bracket expected after list members.")
if count > argMax:
comp.error("Maximum list length exceeded.")
comp.writeChunk(1 - count, opCreateList)
comp.writeChunk(0, count.toDU8())
tkStartList.genRule(parseList, nop, pcNone)
# tables
proc parseTable(comp: Compiler) =
var count: int
while comp.current.tokenType != tkRightBrace:
comp.expression()
comp.consume(tkEqual, "Equal sign expected after key.")
comp.expression()
count.inc()
if comp.current.tokenType != tkRightBrace:
comp.consume(tkComma, "Comma expected after key-value pair.")
comp.consume(tkRightBrace, "Right brace expected after table members.")
if count > argMax:
comp.error("Maximum table length exceeded.")
comp.writeChunk(1 - 2 * count, opCreateTable)
comp.writeChunk(0, count.toDU8())
tkStartTable.genRule(parseTable, nop, pcNone)
# len op
proc parseLen(comp: Compiler) =
comp.expression()
comp.writeChunk(0, opLen)
tkHashtag.genRule(parseLen, nop, pcNone)
# get/set index
proc parseIndex(comp: Compiler) =
# the index
comp.expression()
comp.consume(tkRightBracket, "Right bracket expected after index.")
if comp.match(tkEqual):
comp.parsePrecedence(pcNonAssignTop)
comp.writeChunk(-2, opSetIndex)
else:
comp.writeChunk(-1, opGetIndex)
tkLeftBracket.genRule(nop, parseIndex, pcIndex)
proc parseAmpersand(comp: Compiler) =
# just a simple expression separator
discard

View File

@ -0,0 +1,103 @@
import strformat
import strutils
import bitops # needed for value
import ../scanner
import ../chunk
import ../types/value
import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
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 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>
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.")
# emit call
comp.writeChunk(-argcount, opCall)
comp.writeChunk(0, argcount.uint8)
tkLeftParen.genRule(grouping, parseCall, pcCall)
proc parseFunct(comp: Compiler) =
# jump over
let jumpOverBody = comp.emitJump(1, opFunctionDef)
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)
# 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)