2022-04-04 12:29:23 +02:00
# Copyright 2022 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2022-12-15 16:49:27 +01:00
## The code generator for Peon bytecode
2022-08-17 19:31:27 +02:00
import std / tables
import std / strformat
import std / algorithm
import std / parseutils
import std / strutils
import std / sequtils
import std / sets
import std / os
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
import opcodes
import frontend / compiler / compiler
import frontend / parsing / lexer
import frontend / parsing / parser
import frontend / parsing / ast
import util / multibyte
2022-04-04 12:29:23 +02:00
2022-05-22 15:26:12 +02:00
2022-12-15 11:48:36 +01:00
export opcodes
2022-05-07 10:48:01 +02:00
2022-04-04 12:29:23 +02:00
type
2022-12-15 11:48:36 +01:00
CompilerFunc = object
## An internal compiler function called
## by pragmas
kind : PragmaKind
2023-01-17 12:53:23 +01:00
handler : proc ( self : BytecodeCompiler , pragma : Pragma , name : Name )
2022-11-27 13:39:41 +01:00
2022-04-04 12:29:23 +02:00
Loop = object
## A "loop object" used
## by the compiler to emit
## appropriate jump offsets
## for continue and break
## statements
2022-05-07 10:48:01 +02:00
# Position in the bytecode where the loop starts
start : int
# Scope depth where the loop is located
depth : int
2022-10-17 11:28:00 +02:00
# Jump offsets into our bytecode that we need to
2022-05-07 10:48:01 +02:00
# patch. Used for break statements
2022-10-13 13:12:24 +02:00
breakJumps : seq [ int ]
2022-04-04 12:29:23 +02:00
2022-12-05 08:07:37 +01:00
NamedBlock = ref object
## A "named block object", similar
## to a loop object. Used to emit
## appropriate jump offsets
start : int
depth : int
breakJumps : seq [ int ]
name : string
2022-12-05 08:47:14 +01:00
broken : bool
2022-12-05 08:07:37 +01:00
2022-12-15 11:48:36 +01:00
BytecodeCompiler * = ref object of Compiler
2022-05-04 14:27:15 +02:00
## A wrapper around the Peon compiler's state
2022-04-04 12:29:23 +02:00
# The bytecode chunk where we write code to
chunk : Chunk
# The current loop being compiled (used to
# keep track of where to jump)
currentLoop : Loop
2022-12-05 08:07:37 +01:00
# Stack of named blocks
namedBlocks : seq [ NamedBlock ]
2022-06-14 12:12:56 +02:00
# Compiler procedures called by pragmas
2022-11-23 01:02:35 +01:00
compilerProcs : TableRef [ string , CompilerFunc ]
2022-08-30 12:55:14 +02:00
# Stores the position of all jumps
jumps : seq [ tuple [ patched : bool , offset : int ] ]
2022-11-27 13:39:41 +01:00
# Metadata about function locations
2023-01-17 12:53:23 +01:00
functions : seq [ tuple [ start , stop , pos : int , fn : Name ] ]
forwarded : seq [ tuple [ name : Name , pos : int ] ]
2022-12-04 16:54:18 +01:00
# The topmost occupied stack slot
# in the current frame (0-indexed)
stackIndex : int
2023-03-05 16:49:14 +01:00
lambdas : seq [ LambdaExpr ]
2022-04-04 12:29:23 +02:00
2022-08-30 12:55:14 +02:00
# Forward declarations
2022-12-15 11:48:36 +01:00
proc compile * ( self : BytecodeCompiler , ast : seq [ Declaration ] , file : string , lines : seq [ tuple [ start , stop : int ] ] , source : string , chunk : Chunk = nil ,
2022-11-23 10:40:45 +01:00
incremental : bool = false , isMainModule : bool = true , disabledWarnings : seq [ WarningKind ] = @ [ ] , showMismatches : bool = false ,
mode : CompileMode = Debug ) : Chunk
2022-12-15 11:48:36 +01:00
proc statement ( self : BytecodeCompiler , node : Statement )
proc declaration ( self : BytecodeCompiler , node : Declaration )
2023-05-01 15:30:36 +02:00
proc varDecl ( self : BytecodeCompiler , node : VarDecl )
2022-12-15 11:48:36 +01:00
proc specialize ( self : BytecodeCompiler , typ : Type , args : seq [ Expression ] ) : Type {. discardable . }
proc patchReturnAddress ( self : BytecodeCompiler , pos : int )
2023-01-17 12:53:23 +01:00
proc handleMagicPragma ( self : BytecodeCompiler , pragma : Pragma , name : Name )
proc handlePurePragma ( self : BytecodeCompiler , pragma : Pragma , name : Name )
proc handleErrorPragma ( self : BytecodeCompiler , pragma : Pragma , name : Name )
method dispatchPragmas ( self : BytecodeCompiler , name : Name )
method dispatchDelayedPragmas ( self : BytecodeCompiler , name : Name )
proc funDecl ( self : BytecodeCompiler , node : FunDecl , name : Name )
proc compileModule ( self : BytecodeCompiler , module : Name )
proc generateCall ( self : BytecodeCompiler , fn : Name , args : seq [ Expression ] , line : int )
method prepareFunction ( self : BytecodeCompiler , fn : Name )
2022-08-30 12:55:14 +02:00
# End of forward declarations
2022-04-04 12:29:23 +02:00
2022-06-14 12:12:56 +02:00
2022-12-15 11:48:36 +01:00
proc newBytecodeCompiler * ( replMode : bool = false ) : BytecodeCompiler =
## Initializes a new BytecodeCompiler object
2022-06-14 12:12:56 +02:00
new ( result )
result . ast = @ [ ]
result . current = 0
result . file = " "
result . names = @ [ ]
2022-11-02 13:16:43 +01:00
result . depth = 0
2022-08-15 11:46:24 +02:00
result . lines = @ [ ]
2022-08-30 12:55:14 +02:00
result . jumps = @ [ ]
2023-05-22 15:54:38 +02:00
result . modules = newTable [ string , Name ] ( )
2023-03-05 16:49:14 +01:00
result . lambdas = @ [ ]
2022-06-14 12:12:56 +02:00
result . currentFunction = nil
result . replMode = replMode
2022-11-27 13:39:41 +01:00
result . currentModule = nil
2022-11-23 01:02:35 +01:00
result . compilerProcs = newTable [ string , CompilerFunc ] ( )
result . compilerProcs [ " magic " ] = CompilerFunc ( kind : Immediate , handler : handleMagicPragma )
result . compilerProcs [ " pure " ] = CompilerFunc ( kind : Immediate , handler : handlePurePragma )
result . compilerProcs [ " error " ] = CompilerFunc ( kind : Delayed , handler : handleErrorPragma )
2022-08-15 11:46:24 +02:00
result . source = " "
2022-10-17 11:28:00 +02:00
result . lexer = newLexer ( )
result . lexer . fillSymbolTable ( )
result . parser = newParser ( )
result . isMainModule = false
2022-11-05 14:03:49 +01:00
result . forwarded = @ [ ]
2022-11-22 15:13:42 +01:00
result . disabledWarnings = @ [ ]
2022-11-27 13:39:41 +01:00
result . functions = @ [ ]
2022-12-04 16:54:18 +01:00
result . stackIndex = 1
2022-10-08 15:48:26 +02:00
2023-01-23 01:12:09 +01:00
## Low-level code generation helpers
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc emitByte ( self : BytecodeCompiler , byt : OpCode | uint8 , line : int ) {. inline . } =
2022-04-04 12:29:23 +02:00
## Emits a single byte, writing it to
## the current chunk being compiled
2022-08-30 12:55:14 +02:00
self . chunk . write ( uint8 byt , line )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc emitBytes ( self : BytecodeCompiler , bytarr : openarray [ OpCode | uint8 ] , line : int ) {. inline . } =
2022-05-22 13:02:48 +02:00
## Handy helper method to write arbitrary bytes into
2022-04-04 12:29:23 +02:00
## the current chunk, calling emitByte on each of its
## elements
2022-05-07 10:48:01 +02:00
for b in bytarr :
2022-08-30 12:55:14 +02:00
self . emitByte ( b , line )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc printRepl ( self : BytecodeCompiler , typ : Type , node : Expression ) =
2022-12-04 16:54:18 +01:00
## Emits instruction to print
## peon types in REPL mode
case typ . kind :
of Int64 :
self . emitByte ( PrintInt64 , node . token . line )
of UInt64 :
self . emitByte ( PrintUInt64 , node . token . line )
of Int32 :
self . emitByte ( PrintInt32 , node . token . line )
of UInt32 :
self . emitByte ( PrintInt32 , node . token . line )
of Int16 :
self . emitByte ( PrintInt16 , node . token . line )
of UInt16 :
self . emitByte ( PrintUInt16 , node . token . line )
of Int8 :
self . emitByte ( PrintInt8 , node . token . line )
of UInt8 :
self . emitByte ( PrintUInt8 , node . token . line )
of Float64 :
self . emitByte ( PrintFloat64 , node . token . line )
of Float32 :
self . emitByte ( PrintFloat32 , node . token . line )
of Bool :
self . emitByte ( PrintBool , node . token . line )
2022-12-15 11:48:36 +01:00
of TypeKind . Nan :
2022-12-04 16:54:18 +01:00
self . emitByte ( PrintNan , node . token . line )
2022-12-15 11:48:36 +01:00
of TypeKind . Inf :
2022-12-04 16:54:18 +01:00
self . emitByte ( PrintInf , node . token . line )
2022-12-15 11:48:36 +01:00
of TypeKind . String :
2022-12-04 16:54:18 +01:00
self . emitByte ( PrintString , node . token . line )
else :
self . emitByte ( PrintHex , node . token . line )
2022-12-15 11:48:36 +01:00
proc makeConstant ( self : BytecodeCompiler , val : Expression , typ : Type ) : array [ 3 , uint8 ] =
2022-04-04 12:29:23 +02:00
## Adds a constant to the current chunk's constant table
## and returns its index as a 3-byte array of uint8s
2022-08-18 19:18:29 +02:00
var lit : string
if typ . kind in [ UInt8 , Int8 , Int16 , UInt16 , Int32 , UInt32 , Int64 , UInt64 ] :
lit = val . token . lexeme
if " ' " in lit :
var idx = lit . high ( )
while lit [ idx ] ! = ' \' ' :
lit = lit [ 0 .. ^ 2 ]
dec ( idx )
lit = lit [ 0 .. ^ 2 ]
2022-05-20 15:47:04 +02:00
case typ . kind :
of UInt8 , Int8 :
2022-08-18 19:18:29 +02:00
result = self . chunk . writeConstant ( [ uint8 ( parseInt ( lit ) ) ] )
2022-05-20 15:47:04 +02:00
of Int16 , UInt16 :
2022-08-18 19:18:29 +02:00
result = self . chunk . writeConstant ( parseInt ( lit ) . toDouble ( ) )
2022-05-20 15:47:04 +02:00
of Int32 , UInt32 :
2022-08-18 19:18:29 +02:00
result = self . chunk . writeConstant ( parseInt ( lit ) . toQuad ( ) )
of Int64 :
result = self . chunk . writeConstant ( parseInt ( lit ) . toLong ( ) )
of UInt64 :
result = self . chunk . writeConstant ( parseBiggestUInt ( lit ) . toLong ( ) )
2022-06-02 01:33:56 +02:00
of String :
2022-08-15 17:20:09 +02:00
result = self . chunk . writeConstant ( val . token . lexeme [ 1 .. ^ 1 ] . toBytes ( ) )
2022-06-02 01:33:56 +02:00
of Float32 :
var f : float = 0 .0
discard parseFloat ( val . token . lexeme , f )
result = self . chunk . writeConstant ( cast [ array [ 4 , uint8 ] ] ( float32 ( f ) ) )
of Float64 :
var f : float = 0 .0
discard parseFloat ( val . token . lexeme , f )
result = self . chunk . writeConstant ( cast [ array [ 8 , uint8 ] ] ( f ) )
2022-05-20 15:47:04 +02:00
else :
discard
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc emitConstant ( self : BytecodeCompiler , obj : Expression , kind : Type ) =
2022-05-29 15:54:01 +02:00
## Emits a constant instruction along
2022-04-04 12:29:23 +02:00
## with its operand
2022-06-02 01:33:56 +02:00
case kind . kind :
2022-05-02 17:26:38 +02:00
of Int64 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadInt64 , obj . token . line )
2022-05-29 15:54:01 +02:00
of UInt64 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadUInt64 , obj . token . line )
2022-05-29 15:54:01 +02:00
of Int32 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadInt32 , obj . token . line )
2022-06-02 01:33:56 +02:00
of UInt32 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadUInt32 , obj . token . line )
2022-06-02 01:33:56 +02:00
of Int16 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadInt16 , obj . token . line )
2022-06-02 01:33:56 +02:00
of UInt16 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadUInt16 , obj . token . line )
2022-06-02 01:33:56 +02:00
of Int8 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadInt8 , obj . token . line )
2022-06-02 01:33:56 +02:00
of UInt8 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadUInt8 , obj . token . line )
2022-06-02 01:33:56 +02:00
of String :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadString , obj . token . line )
2022-06-02 01:33:56 +02:00
let str = LiteralExpr ( obj ) . literal . lexeme
if str . len ( ) > = 16777216 :
2022-06-21 20:18:53 +02:00
self . error ( " string constants cannot be larger than 16777215 bytes " )
2022-08-30 12:55:14 +02:00
self . emitBytes ( ( str . len ( ) - 2 ) . toTriple ( ) , obj . token . line )
2022-06-02 01:33:56 +02:00
of Float32 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadFloat32 , obj . token . line )
2022-06-02 01:33:56 +02:00
of Float64 :
2022-08-30 12:55:14 +02:00
self . emitByte ( LoadFloat64 , obj . token . line )
2022-05-02 17:26:38 +02:00
else :
2022-05-18 13:32:32 +02:00
discard # TODO
2022-08-30 12:55:14 +02:00
self . emitBytes ( self . makeConstant ( obj , kind ) , obj . token . line )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc setJump ( self : BytecodeCompiler , offset : int , jmp : array [ 3 , uint8 ] ) =
2022-08-30 12:55:14 +02:00
## Sets a jump at the given
## offset to the given value
self . chunk . code [ offset + 1 ] = jmp [ 0 ]
self . chunk . code [ offset + 2 ] = jmp [ 1 ]
self . chunk . code [ offset + 3 ] = jmp [ 2 ]
2022-12-15 11:48:36 +01:00
proc setJump ( self : BytecodeCompiler , offset : int , jmp : seq [ uint8 ] ) =
2022-08-30 12:55:14 +02:00
## Sets a jump at the given
## offset to the given value
self . chunk . code [ offset + 1 ] = jmp [ 0 ]
self . chunk . code [ offset + 2 ] = jmp [ 1 ]
self . chunk . code [ offset + 3 ] = jmp [ 2 ]
2022-12-04 16:54:18 +01:00
2022-08-30 12:55:14 +02:00
2022-12-15 11:48:36 +01:00
proc emitJump ( self : BytecodeCompiler , opcode : OpCode , line : int ) : int =
2022-08-30 12:55:14 +02:00
## Emits a dummy jump offset to be patched later
## and returns a unique identifier for that jump
## to be passed to patchJump
self . emitByte ( opcode , line )
self . jumps . add ( ( patched : false , offset : self . chunk . code . high ( ) ) )
self . emitBytes ( 0 . toTriple ( ) , line )
result = self . jumps . high ( )
2022-12-15 11:48:36 +01:00
proc fixFunctionOffsets ( self : BytecodeCompiler , where , oldLen : int ) =
2022-11-27 13:39:41 +01:00
## Fixes function offsets after the size of our
2022-08-30 12:55:14 +02:00
## bytecode has changed
if oldLen = = self . chunk . code . len ( ) :
return
let offset = self . chunk . code . len ( ) - oldLen
2022-11-27 13:39:41 +01:00
var newOffset : array [ 3 , uint8 ]
2022-08-30 12:55:14 +02:00
var tmp : int
2022-10-24 13:53:27 +02:00
var i = 0
2022-11-27 13:39:41 +01:00
for function in self . functions . mitems ( ) :
if function . start > = where :
newOffset = ( function . start + offset ) . toTriple ( )
self . chunk . functions [ function . pos ] = newOffset [ 0 ]
self . chunk . functions [ function . pos + 1 ] = newOffset [ 1 ]
self . chunk . functions [ function . pos + 2 ] = newOffset [ 2 ]
tmp = [ self . chunk . functions [ function . pos + 3 ] , self . chunk . functions [ function . pos + 4 ] , self . chunk . functions [ function . pos + 5 ] ] . fromTriple ( ) . int
newOffset = ( tmp + offset ) . toTriple ( )
self . chunk . functions [ function . pos + 3 ] = newOffset [ 0 ]
self . chunk . functions [ function . pos + 4 ] = newOffset [ 1 ]
self . chunk . functions [ function . pos + 5 ] = newOffset [ 2 ]
function . start + = offset
function . stop + = offset
2022-10-24 13:53:27 +02:00
inc ( i )
2022-08-30 12:55:14 +02:00
2022-12-15 11:48:36 +01:00
proc fixJumps ( self : BytecodeCompiler , where , oldLen : int ) =
2022-08-30 12:55:14 +02:00
## Fixes jump offsets after the size
## of our bytecode has changed
if oldLen = = self . chunk . code . len ( ) :
return
let offset = self . chunk . code . len ( ) - oldLen
for jump in self . jumps . mitems ( ) :
2022-11-27 13:39:41 +01:00
if jump . offset > = where :
2022-08-30 12:55:14 +02:00
# While all already-patched jumps need
# to have their jump offsets fixed, we
# also need to update our internal jumps
# list in cases where we shifted the jump
# instruction itself into the code!
jump . offset + = offset
2022-10-08 09:18:35 +02:00
self . setJump ( jump . offset , self . chunk . code [ jump . offset + 1 .. jump . offset + 3 ] )
2022-04-04 12:29:23 +02:00
2022-05-02 17:26:38 +02:00
2022-12-15 11:48:36 +01:00
proc fixLines ( self : BytecodeCompiler , where , count : int , added : bool = true ) =
2022-11-27 13:39:41 +01:00
## Fixes the line metadatata of our
## bytecode chunk after the size of
## the code segment has changed. The
## "count" argument represents how
## many bytes were added or deleted
## from the code and the "added" argument
## tells fixLines that either count
## instructions were injected (added = true,
## the default) or that count instructions
## were removed (added = false). The where
2022-12-01 22:04:10 +01:00
## argument is the position where the code
## change was performed
2022-11-27 13:39:41 +01:00
if added :
2022-12-01 22:04:10 +01:00
# We don't do any bounds checking here because I doubt
# there's ever going to be even close to int.high()
# instructions on a line :P
2022-11-27 13:39:41 +01:00
inc ( self . chunk . lines [ self . chunk . getIdx ( self . chunk . getLine ( where ) ) + 1 ] , count )
else :
if self . chunk . lines [ self . chunk . getIdx ( self . chunk . getLine ( where ) ) + 1 ] > 0 :
dec ( self . chunk . lines [ self . chunk . getIdx ( self . chunk . getLine ( where ) ) + 1 ] , count )
2022-12-15 11:48:36 +01:00
proc fixNames ( self : BytecodeCompiler , where , oldLen : int ) =
2022-12-01 22:04:10 +01:00
## Fixes the codePos field of our name objects
## after the size of the bytecode has changed
2022-11-27 13:39:41 +01:00
let offset = self . chunk . code . len ( ) - oldLen
for name in self . names :
2023-01-17 12:53:23 +01:00
if name . codePos > where :
name . codePos + = offset
2022-12-02 13:35:54 +01:00
if name . valueType . kind = = Function :
name . valueType . location + = offset
2022-11-27 13:39:41 +01:00
2023-01-05 12:44:11 +01:00
proc insertAt ( self : BytecodeCompiler , where : int , opcode : OpCode , data : openarray [ uint8 ] ) : int {. used . } =
2022-11-27 13:39:41 +01:00
## Inserts the given instruction into the
## chunk's code segment and updates internal
2022-12-04 16:54:18 +01:00
## metadata to reflect this change. Returns
## the new location where the code was added
## plus one (useful for consecutive calls)
2022-11-27 13:39:41 +01:00
result = where
let oldLen = self . chunk . code . len ( )
self . chunk . code . insert ( uint8 ( opcode ) , where )
inc ( result )
for i , item in data :
self . chunk . code . insert ( item , where + i + 1 )
inc ( result )
# Changing the size of our code segment forces us
# to update all metadata that refers to a position
# into it
self . fixJumps ( where , oldLen )
self . fixLines ( where , self . chunk . code . len ( ) - oldLen , true )
self . fixNames ( where , oldLen )
2022-12-01 22:04:10 +01:00
self . fixFunctionOffsets ( oldLen , where )
2022-11-27 13:39:41 +01:00
2023-01-26 12:11:29 +01:00
proc patchJump ( self : BytecodeCompiler , offset : int ) =
## Patches a previously emitted relative
## jump using emitJump
var jump : int = self . chunk . code . len ( ) - self . jumps [ offset ] . offset
if jump < 0 :
self . error ( " jump size cannot be negative (This is an internal error and most likely a bug) " )
if jump > 16777215 :
# TODO: Emit consecutive jumps using insertAt
self . error ( " cannot jump more than 16777215 instructions " )
if jump > 0 :
self . setJump ( self . jumps [ offset ] . offset , ( jump - 4 ) . toTriple ( ) )
self . jumps [ offset ] . patched = true
2022-12-15 11:48:36 +01:00
proc handleBuiltinFunction ( self : BytecodeCompiler , fn : Type , args : seq [ Expression ] , line : int ) =
2022-07-09 12:47:53 +02:00
## Emits instructions for builtin functions
2022-06-14 18:10:13 +02:00
## such as addition or subtraction
2022-10-13 13:12:24 +02:00
if fn . builtinOp notin [ " LogicalOr " , " LogicalAnd " ] :
2022-07-10 13:19:57 +02:00
if len ( args ) = = 2 :
self . expression ( args [ 1 ] )
2022-08-01 10:36:06 +02:00
self . expression ( args [ 0 ] )
elif len ( args ) = = 1 :
self . expression ( args [ 0 ] )
2022-08-17 17:31:15 +02:00
const codes : Table [ string , OpCode ] = { " Negate " : Negate ,
" NegateFloat32 " : NegateFloat32 ,
" NegateFloat64 " : NegateFloat64 ,
" Add " : Add ,
" Subtract " : Subtract ,
" Divide " : Divide ,
" Multiply " : Multiply ,
" SignedDivide " : SignedDivide ,
" AddFloat64 " : AddFloat64 ,
" SubtractFloat64 " : SubtractFloat64 ,
" DivideFloat64 " : DivideFloat64 ,
" MultiplyFloat64 " : MultiplyFloat64 ,
" AddFloat32 " : AddFloat32 ,
" SubtractFloat32 " : SubtractFloat32 ,
" DivideFloat32 " : DivideFloat32 ,
" MultiplyFloat32 " : MultiplyFloat32 ,
" Pow " : Pow ,
" SignedPow " : SignedPow ,
" PowFloat32 " : PowFloat32 ,
" PowFloat64 " : PowFloat64 ,
" Mod " : Mod ,
" SignedMod " : SignedMod ,
" ModFloat32 " : ModFloat32 ,
" ModFloat64 " : ModFloat64 ,
" Or " : Or ,
" And " : And ,
" Xor " : Xor ,
" Not " : Not ,
" LShift " : LShift ,
" RShift " : RShift ,
" Equal " : Equal ,
" NotEqual " : NotEqual ,
" LessThan " : LessThan ,
" GreaterThan " : GreaterThan ,
" LessOrEqual " : LessOrEqual ,
" GreaterOrEqual " : GreaterOrEqual ,
2022-12-04 16:54:18 +01:00
" SignedLessThan " : SignedLessThan ,
" SignedGreaterThan " : SignedGreaterThan ,
" SignedLessOrEqual " : SignedLessOrEqual ,
" SignedGreaterOrEqual " : SignedGreaterOrEqual ,
" Float32LessThan " : Float32LessThan ,
" Float32GreaterThan " : Float32GreaterThan ,
" Float32LessOrEqual " : Float32LessOrEqual ,
" Float32GreaterOrEqual " : Float32GreaterOrEqual ,
" Float64LessThan " : Float64LessThan ,
" Float64GreaterThan " : Float64GreaterThan ,
" Float64LessOrEqual " : Float64LessOrEqual ,
" Float64GreaterOrEqual " : Float64GreaterOrEqual ,
2022-08-17 17:31:15 +02:00
" PrintString " : PrintString ,
2022-10-13 13:12:24 +02:00
" SysClock64 " : SysClock64 ,
2022-10-13 18:34:11 +02:00
" LogicalNot " : LogicalNot ,
2023-05-01 16:37:33 +02:00
" NegInf " : LoadNInf ,
" Identity " : Identity
2022-08-17 17:31:15 +02:00
} . to_table ( )
2022-12-01 22:04:10 +01:00
if fn . builtinOp = = " print " :
2023-06-01 12:56:59 +02:00
var typ = self . inferOrError ( args [ 0 ] )
2022-12-01 22:04:10 +01:00
case typ . kind :
of Int64 :
self . emitByte ( PrintInt64 , line )
of Int32 :
self . emitByte ( PrintInt32 , line )
of Int16 :
self . emitByte ( PrintInt16 , line )
of Int8 :
self . emitByte ( PrintInt8 , line )
of UInt64 :
self . emitByte ( PrintUInt64 , line )
of UInt32 :
self . emitByte ( PrintUInt32 , line )
of UInt16 :
self . emitByte ( PrintUInt16 , line )
of UInt8 :
self . emitByte ( PrintUInt8 , line )
of Float64 :
self . emitByte ( PrintFloat64 , line )
of Float32 :
self . emitByte ( PrintFloat32 , line )
of String :
self . emitByte ( PrintString , line )
of Bool :
self . emitByte ( PrintBool , line )
2022-12-15 11:48:36 +01:00
of TypeKind . Nan :
2022-12-01 22:04:10 +01:00
self . emitByte ( PrintNan , line )
2022-12-15 11:48:36 +01:00
of TypeKind . Inf :
2022-12-01 22:04:10 +01:00
self . emitByte ( PrintInf , line )
2022-12-02 13:35:54 +01:00
of Function :
2022-12-04 16:54:18 +01:00
self . emitByte ( LoadString , line )
var loc : string = typ . location . toHex ( )
while loc [ 0 ] = = ' 0 ' and loc . len ( ) > 1 :
loc = loc [ 1 .. ^ 1 ]
var str : string
if typ . isLambda :
str = & " anonymous function at 0x{loc} "
else :
str = & " function ' {FunDecl(typ.fun).name.token.lexeme} ' at 0x{loc} "
self . emitBytes ( str . len ( ) . toTriple ( ) , line )
self . emitBytes ( self . chunk . writeConstant ( str . toBytes ( ) ) , line )
self . emitByte ( PrintString , line )
2022-12-01 22:04:10 +01:00
else :
2022-12-04 16:54:18 +01:00
self . error ( & " invalid type {self.stringify(typ)} for built-in ' print ' " , args [ 0 ] )
2022-12-01 22:04:10 +01:00
return
2022-10-13 13:12:24 +02:00
if fn . builtinOp in codes :
self . emitByte ( codes [ fn . builtinOp ] , line )
2022-08-17 17:31:15 +02:00
return
# Some builtin operations are slightly more complex
# so we handle them separately
2022-10-13 13:12:24 +02:00
case fn . builtinOp :
2022-07-09 12:47:53 +02:00
of " LogicalOr " :
2022-06-14 22:45:32 +02:00
self . expression ( args [ 0 ] )
2022-10-13 13:12:24 +02:00
let jump = self . emitJump ( JumpIfTrue , line )
2022-06-14 22:45:32 +02:00
self . expression ( args [ 1 ] )
self . patchJump ( jump )
2022-07-09 12:47:53 +02:00
of " LogicalAnd " :
2022-06-14 22:45:32 +02:00
self . expression ( args [ 0 ] )
2022-12-05 17:09:09 +01:00
let jump = self . emitJump ( JumpIfFalseOrPop , line )
2022-06-14 22:45:32 +02:00
self . expression ( args [ 1 ] )
self . patchJump ( jump )
2023-06-01 12:56:59 +02:00
of " cast " :
# Type casts are a merely compile-time construct:
# they don't produce any code at runtime because
# the underlying data representation does not change!
# The only reason why there's a "cast" pragma is to
# make it so that the peon stub can have no body
discard
2022-06-14 18:10:13 +02:00
else :
2022-10-13 13:12:24 +02:00
self . error ( & " unknown built-in: ' {fn.builtinOp} ' " , fn . fun )
2022-05-30 22:06:15 +02:00
2022-12-15 11:48:36 +01:00
proc patchForwardDeclarations ( self : BytecodeCompiler ) =
2022-11-05 14:03:49 +01:00
## Patches forward declarations and looks
## for their implementations so that calls
## to them work properly
2023-01-17 12:53:23 +01:00
var impl : Name
2022-11-05 14:03:49 +01:00
var pos : array [ 8 , uint8 ]
for ( forwarded , position ) in self . forwarded :
2023-01-17 12:53:23 +01:00
impl = self . match ( forwarded . ident . token . lexeme , forwarded . valueType , allowFwd = false )
2023-01-24 12:08:29 +01:00
if forwarded . isPrivate ! = impl . isPrivate :
self . error ( & " implementation of ' {impl.ident.token.lexeme} ' has a mismatching visibility modifier from its forward declaration " , impl . ident )
2022-11-05 14:03:49 +01:00
if position = = 0 :
2023-06-01 12:56:59 +02:00
# Forward declaration created by funDecl (it's
# necessary to make sure that there's no unimplemented
# forward declarations)
2022-11-05 14:03:49 +01:00
continue
pos = impl . codePos . toLong ( )
self . chunk . consts [ position ] = pos [ 0 ]
self . chunk . consts [ position + 1 ] = pos [ 1 ]
self . chunk . consts [ position + 2 ] = pos [ 2 ]
self . chunk . consts [ position + 3 ] = pos [ 3 ]
self . chunk . consts [ position + 4 ] = pos [ 4 ]
self . chunk . consts [ position + 5 ] = pos [ 5 ]
self . chunk . consts [ position + 6 ] = pos [ 6 ]
self . chunk . consts [ position + 7 ] = pos [ 7 ]
2022-08-30 12:55:14 +02:00
2022-12-15 11:48:36 +01:00
proc endScope ( self : BytecodeCompiler ) =
2022-08-30 12:55:14 +02:00
## Ends the current local scope
2022-11-02 13:16:43 +01:00
if self . depth < 0 :
self . error ( " cannot call endScope with depth < 0 (This is an internal error and most likely a bug) " )
dec ( self . depth )
2022-11-23 01:02:35 +01:00
# We keep track both of which names are going out of scope
# and how many actually need to be popped off the call stack
# at runtime (since only variables and function arguments
# actually materialize at runtime)
2022-08-30 12:55:14 +02:00
var names : seq [ Name ] = @ [ ]
var popCount = 0
for name in self . names :
2023-03-27 17:57:18 +02:00
if self . replMode and name . depth = = 0 :
continue
2022-11-23 01:02:35 +01:00
# We only pop names in scopes deeper than ours
2022-11-02 13:16:43 +01:00
if name . depth > self . depth :
2022-11-23 01:02:35 +01:00
if name . depth = = 0 and not self . isMainModule :
# Global names coming from other modules only go out of scope
# when the global scope of the main module is closed (i.e. at
# the end of the whole program)
2022-10-13 16:52:37 +02:00
continue
2022-11-23 01:02:35 +01:00
names . add ( name )
# Now we have to actually emit the pop instructions. First
# off, we skip the names that will not exist at runtime,
# because there's no need to emit any instructions to pop them
# (we still remove them from the name list later so they can't
# be referenced anymore, of course)
if name . kind notin [ NameKind . Var , NameKind . Argument ] :
continue
2022-12-04 16:54:18 +01:00
elif name . kind = = NameKind . Argument and not name . belongsTo . isNil ( ) :
2023-06-01 12:56:59 +02:00
if name . belongsTo . valueType . isBuiltin :
2022-11-23 01:02:35 +01:00
# Arguments to builtin functions become temporaries on the
# stack and are popped automatically
continue
2022-12-06 10:59:05 +01:00
if name . belongsTo . valueType . isAuto :
# Automatic functions do not materialize
# at runtime, so their arguments don't either
2022-11-23 01:02:35 +01:00
continue
2023-03-04 12:13:19 +01:00
# This name has been generated internally by the
# compiler and is a copy of an already existing
# one, so we only need to pop its "real" counterpart
if not name . isReal :
continue
2022-12-04 16:54:18 +01:00
inc ( popCount )
2022-11-28 14:04:12 +01:00
if not name . resolved :
2023-01-24 12:19:06 +01:00
# We emit warnings for names that are declared but never used
2022-11-23 01:02:35 +01:00
case name . kind :
of NameKind . Var :
2022-11-27 13:39:41 +01:00
if not name . ident . token . lexeme . startsWith ( " _ " ) and name . isPrivate :
2022-11-23 01:02:35 +01:00
self . warning ( UnusedName , & " ' {name.ident.token.lexeme} ' is declared but not used (add ' _ ' prefix to silence warning) " , name )
of NameKind . Argument :
2022-11-27 13:39:41 +01:00
if not name . ident . token . lexeme . startsWith ( " _ " ) and name . isPrivate :
2023-06-01 12:56:59 +02:00
if not name . belongsTo . isNil ( ) and not name . belongsTo . valueType . isBuiltin and name . belongsTo . isReal and name . belongsTo . resolved :
2022-11-28 18:21:38 +01:00
# Builtin functions never use their arguments. We also don't emit this
# warning if the function was generated internally by the compiler (for
# example as a result of generic specialization) because such objects do
# not exist in the user's code and are likely duplicated anyway
2022-11-23 01:02:35 +01:00
self . warning ( UnusedName , & " argument ' {name.ident.token.lexeme} ' is unused (add ' _ ' prefix to silence warning) " , name )
else :
discard
2022-12-04 16:54:18 +01:00
dec ( self . stackIndex , popCount )
2022-08-30 12:55:14 +02:00
if popCount > 1 :
2022-11-02 12:03:14 +01:00
# If we're popping more than one variable,
# we emit a bunch of PopN instructions until
# the pop count is greater than zero
while popCount > 0 :
self . emitByte ( PopN , self . peek ( ) . token . line )
self . emitBytes ( popCount . toDouble ( ) , self . peek ( ) . token . line )
popCount - = popCount . toDouble ( ) . fromDouble ( ) . int
2022-08-30 12:55:14 +02:00
elif popCount = = 1 :
# We only emit PopN if we're popping more than one value
self . emitByte ( PopC , self . peek ( ) . token . line )
# This seems *really* slow, but
# what else should I do? Nim doesn't
# allow the removal of items during
# seq iteration so ¯\_(ツ)_/¯
var idx = 0
while idx < self . names . len ( ) :
for name in names :
if self . names [ idx ] = = name :
self . names . delete ( idx )
inc ( idx )
2022-05-24 09:55:08 +02:00
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc emitLoop ( self : BytecodeCompiler , begin : int , line : int ) =
2022-08-30 12:55:14 +02:00
## Emits a JumpBackwards instruction with the correct
## jump offset
let offset = self . chunk . code . high ( ) - begin + 4
if offset > 16777215 :
2022-10-13 16:52:37 +02:00
# TODO: Emit consecutive jumps?
2022-08-30 12:55:14 +02:00
self . error ( " cannot jump more than 16777215 bytecode instructions " )
self . emitByte ( JumpBackwards , line )
self . emitBytes ( offset . toTriple ( ) , line )
2022-06-13 15:04:53 +02:00
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc patchBreaks ( self : BytecodeCompiler ) =
2022-08-30 12:55:14 +02:00
## Patches the jumps emitted by
## breakStmt. This is needed
## because the size of code
## to skip is not known before
## the loop is fully compiled
2022-10-13 13:12:24 +02:00
for brk in self . currentLoop . breakJumps :
2022-08-30 12:55:14 +02:00
self . patchJump ( brk )
2022-12-05 08:07:37 +01:00
for blk in self . namedBlocks :
for brk in blk . breakJumps :
self . patchJump ( brk )
2022-04-04 12:29:23 +02:00
2023-01-17 12:53:23 +01:00
proc handleMagicPragma ( self : BytecodeCompiler , pragma : Pragma , name : Name ) =
2022-08-30 12:55:14 +02:00
## Handles the "magic" pragma. Assumes the given name is already
## declared
if pragma . args . len ( ) ! = 1 :
2023-06-01 12:56:59 +02:00
self . error ( & " ' magic ' pragma: wrong number of arguments (expected 1, got {len(pragma.args)}) " )
2022-08-30 12:55:14 +02:00
elif pragma . args [ 0 ] . kind ! = strExpr :
2023-06-01 12:56:59 +02:00
self . error ( & " ' magic ' pragma: wrong argument type (constant string expected, got {self.stringify(self.inferOrError(pragma.args[0]))}) " )
2022-11-29 16:48:05 +01:00
elif name . node . kind = = NodeKind . funDecl :
2023-06-01 12:56:59 +02:00
name . valueType . isBuiltin = true
2022-11-29 16:48:05 +01:00
name . valueType . builtinOp = pragma . args [ 0 ] . token . lexeme [ 1 .. ^ 2 ]
2023-06-01 12:56:59 +02:00
name . valueType . compiled = true
2022-11-29 16:48:05 +01:00
elif name . node . kind = = NodeKind . typeDecl :
2022-12-01 22:04:10 +01:00
name . valueType = pragma . args [ 0 ] . token . lexeme [ 1 .. ^ 2 ] . toIntrinsic ( )
if name . valueType . kind = = All :
2023-06-01 12:56:59 +02:00
self . error ( " don ' t even think about it (compiler-chan is angry at you :/) " , pragma )
2023-03-05 16:49:14 +01:00
if name . valueType . isNil ( ) :
self . error ( " ' magic ' pragma: wrong argument value " , pragma . args [ 0 ] )
2023-06-01 12:56:59 +02:00
name . valueType . isBuiltin = true
2022-11-29 16:48:05 +01:00
else :
2022-08-30 12:55:14 +02:00
self . error ( " ' magic ' pragma is not valid in this context " )
2022-05-30 09:29:03 +02:00
2022-04-04 12:29:23 +02:00
2023-01-17 12:53:23 +01:00
proc handleErrorPragma ( self : BytecodeCompiler , pragma : Pragma , name : Name ) =
2022-11-23 01:02:35 +01:00
## Handles the "error" pragma
if pragma . args . len ( ) ! = 1 :
self . error ( " ' error ' pragma: wrong number of arguments " )
elif pragma . args [ 0 ] . kind ! = strExpr :
self . error ( " ' error ' pragma: wrong type of argument (constant string expected) " )
elif not name . isNil ( ) and name . node . kind ! = NodeKind . funDecl :
self . error ( " ' error ' pragma is not valid in this context " )
self . error ( pragma . args [ 0 ] . token . lexeme [ 1 .. ^ 2 ] )
2023-01-17 12:53:23 +01:00
proc handlePurePragma ( self : BytecodeCompiler , pragma : Pragma , name : Name ) =
2022-08-30 12:55:14 +02:00
## Handles the "pure" pragma
2022-11-05 10:57:28 +01:00
case name . node . kind :
2022-08-30 12:55:14 +02:00
of NodeKind . funDecl :
2022-11-05 10:57:28 +01:00
FunDecl ( name . node ) . isPure = true
2022-12-02 13:35:54 +01:00
of NodeKind . lambdaExpr :
2022-11-05 10:57:28 +01:00
LambdaExpr ( name . node ) . isPure = true
2022-08-30 12:55:14 +02:00
else :
self . error ( " ' pure ' pragma is not valid in this context " )
2023-01-17 12:53:23 +01:00
method dispatchPragmas ( self : BytecodeCompiler , name : Name ) =
2022-08-30 12:55:14 +02:00
## Dispatches pragmas bound to objects
2022-11-05 10:57:28 +01:00
if name . node . isNil ( ) :
return
2022-08-30 12:55:14 +02:00
var pragmas : seq [ Pragma ] = @ [ ]
2022-11-05 10:57:28 +01:00
case name . node . kind :
2022-08-30 12:55:14 +02:00
of NodeKind . funDecl , NodeKind . typeDecl , NodeKind . varDecl :
2022-11-05 10:57:28 +01:00
pragmas = Declaration ( name . node ) . pragmas
2022-12-02 13:35:54 +01:00
of NodeKind . lambdaExpr :
2022-11-05 10:57:28 +01:00
pragmas = LambdaExpr ( name . node ) . pragmas
2022-08-30 12:55:14 +02:00
else :
discard # Unreachable
2022-11-23 01:02:35 +01:00
var f : CompilerFunc
2022-08-30 12:55:14 +02:00
for pragma in pragmas :
if pragma . name . token . lexeme notin self . compilerProcs :
self . error ( & " unknown pragma ' {pragma.name.token.lexeme} ' " )
2022-11-23 01:02:35 +01:00
f = self . compilerProcs [ pragma . name . token . lexeme ]
if f . kind ! = Immediate :
continue
f . handler ( self , pragma , name )
2023-01-17 12:53:23 +01:00
method dispatchDelayedPragmas ( self : BytecodeCompiler , name : Name ) =
2022-11-23 01:02:35 +01:00
## Dispatches pragmas bound to objects once they
## are called. Only applies to functions
if name . node . isNil ( ) :
return
var pragmas : seq [ Pragma ] = @ [ ]
pragmas = Declaration ( name . node ) . pragmas
var f : CompilerFunc
for pragma in pragmas :
if pragma . name . token . lexeme notin self . compilerProcs :
self . error ( & " unknown pragma ' {pragma.name.token.lexeme} ' " )
f = self . compilerProcs [ pragma . name . token . lexeme ]
if f . kind = = Immediate :
continue
f . handler ( self , pragma , name )
2022-08-30 12:55:14 +02:00
2022-12-15 11:48:36 +01:00
proc patchReturnAddress ( self : BytecodeCompiler , pos : int ) =
2022-08-30 12:55:14 +02:00
## Patches the return address of a function
## call
2022-10-13 13:12:24 +02:00
let address = self . chunk . code . len ( ) . toLong ( )
2022-08-30 12:55:14 +02:00
self . chunk . consts [ pos ] = address [ 0 ]
self . chunk . consts [ pos + 1 ] = address [ 1 ]
self . chunk . consts [ pos + 2 ] = address [ 2 ]
self . chunk . consts [ pos + 3 ] = address [ 3 ]
2022-10-13 13:12:24 +02:00
self . chunk . consts [ pos + 4 ] = address [ 4 ]
self . chunk . consts [ pos + 5 ] = address [ 5 ]
self . chunk . consts [ pos + 6 ] = address [ 6 ]
self . chunk . consts [ pos + 7 ] = address [ 7 ]
2022-08-30 12:55:14 +02:00
2022-12-15 13:22:34 +01:00
proc generateCall ( self : BytecodeCompiler , fn : Type , args : seq [ Expression ] , line : int ) {. used . } =
## Version of generateCall that takes Type objects
## instead of Name objects (used for lambdas and
## consequent calls). The function's address is
## assumed to be on the stack
2023-06-01 12:56:59 +02:00
if fn . isBuiltin :
self . handleBuiltinFunction ( fn , args , line )
return
2022-12-15 13:22:34 +01:00
self . emitByte ( LoadUInt64 , line )
self . emitBytes ( self . chunk . writeConstant ( 0 . toLong ( ) ) , line )
let pos = self . chunk . consts . len ( ) - 8
for i , argument in reversed ( args ) :
# We pass the arguments in reverse
# because of how stacks work. They'll
# be reversed again at runtime
self . check ( argument , fn . args [ ^ ( i + 1 ) ] . kind )
self . expression ( argument )
# Creates a new call frame and jumps
# to the function's first instruction
# in the code
self . emitByte ( Call , line )
self . emitBytes ( args . len ( ) . toTriple ( ) , line )
2022-10-17 11:28:00 +02:00
self . patchReturnAddress ( pos )
2022-08-30 12:55:14 +02:00
2023-01-17 12:53:23 +01:00
method prepareFunction ( self : BytecodeCompiler , fn : Name ) =
2022-12-15 13:22:34 +01:00
## "Prepares" a function declaration by declaring
## its arguments and typechecking it
# First we declare the function's generics, if it has any.
# This is because the function's return type may in itself
# be a generic, so it needs to exist first
var constraints : seq [ tuple [ match : bool , kind : Type ] ] = @ [ ]
for gen in fn . node . generics :
2023-06-01 12:56:59 +02:00
self . unpackTypes ( gen . cond , constraints )
2023-01-17 12:53:23 +01:00
self . names . add ( Name ( depth : fn . depth + 1 ,
2022-11-27 13:39:41 +01:00
isPrivate : true ,
2022-12-15 13:22:34 +01:00
valueType : Type ( kind : Generic , name : gen . name . token . lexeme , cond : constraints ) ,
codePos : 0 ,
2022-11-27 13:39:41 +01:00
isLet : false ,
2022-12-15 13:22:34 +01:00
line : fn . node . token . line ,
belongsTo : fn ,
ident : gen . name ,
owner : self . currentModule ,
file : self . file ) )
constraints = @ [ ]
# We now declare and typecheck the function's
# arguments
let idx = self . stackIndex
self . stackIndex = 1
var default : Expression
2023-06-01 12:56:59 +02:00
let node = FunDecl ( fn . node )
2022-12-15 13:22:34 +01:00
var i = 0
2023-06-01 12:56:59 +02:00
var typ : Type
2022-12-15 13:22:34 +01:00
for argument in node . arguments :
if self . names . high ( ) > 16777215 :
self . error ( " cannot declare more than 16777215 variables at a time " )
inc ( self . stackIndex )
2023-06-01 12:56:59 +02:00
typ = self . inferOrError ( argument . valueType )
if typ . kind = = Typevar :
typ = typ . wrapped
2023-01-17 12:53:23 +01:00
self . names . add ( Name ( depth : fn . depth + 1 ,
2022-12-15 13:22:34 +01:00
isPrivate : true ,
owner : fn . owner ,
file : fn . file ,
isConst : false ,
ident : argument . name ,
2023-06-01 12:56:59 +02:00
valueType : typ ,
2022-11-27 13:39:41 +01:00
codePos : 0 ,
2022-12-15 13:22:34 +01:00
isLet : false ,
line : argument . name . token . line ,
belongsTo : fn ,
kind : NameKind . Argument ,
node : argument . name ,
position : self . stackIndex ,
isReal : not node . isTemplate
) )
if node . arguments . high ( ) - node . defaults . high ( ) < = node . arguments . high ( ) :
# There's a default argument!
2023-06-01 12:56:59 +02:00
fn . valueType . args . add ( ( self . names [ ^ 1 ] . ident . token . lexeme , typ , node . defaults [ i ] ) )
2022-12-15 13:22:34 +01:00
inc ( i )
else :
# This argument has no default
2023-06-01 12:56:59 +02:00
fn . valueType . args . add ( ( self . names [ ^ 1 ] . ident . token . lexeme , typ , default ) )
2022-12-15 13:22:34 +01:00
# The function needs a return type too!
2023-06-01 12:56:59 +02:00
if not node . returnType . isNil ( ) :
fn . valueType . returnType = self . inferOrError ( node . returnType )
if fn . valueType . returnType . kind = = Typevar :
fn . valueType . returnType = fn . valueType . returnType . wrapped
2022-12-15 13:22:34 +01:00
fn . position = self . stackIndex
self . stackIndex = idx
if node . isTemplate :
fn . valueType . compiled = true
2022-08-30 12:55:14 +02:00
2023-01-17 12:53:23 +01:00
proc prepareAutoFunction ( self : BytecodeCompiler , fn : Name , args : seq [ tuple [ name : string , kind : Type , default : Expression ] ] ) : Name =
2022-12-15 13:22:34 +01:00
## "Prepares" an automatic function declaration
## by declaring a concrete version of it along
## with its arguments
let idx = self . stackIndex
self . stackIndex = 1
var default : Expression
var node = FunDecl ( fn . node )
var fn = deepCopy ( fn )
fn . valueType . isAuto = false
fn . valueType . compiled = false
self . names . add ( fn )
2023-01-23 01:12:09 +01:00
# We now declare and typecheck the function's
# arguments
2022-12-15 13:22:34 +01:00
for ( argument , val ) in zip ( node . arguments , args ) :
if self . names . high ( ) > 16777215 :
self . error ( " cannot declare more than 16777215 variables at a time " )
inc ( self . stackIndex )
2023-01-17 12:53:23 +01:00
self . names . add ( Name ( depth : fn . depth + 1 ,
2022-12-15 13:22:34 +01:00
isPrivate : true ,
owner : fn . owner ,
file : fn . file ,
isConst : false ,
ident : argument . name ,
valueType : val . kind ,
codePos : 0 ,
isLet : false ,
line : argument . name . token . line ,
belongsTo : fn ,
kind : NameKind . Argument ,
node : argument . name ,
position : self . stackIndex ,
isReal : not node . isTemplate
) )
if node . isTemplate :
fn . valueType . compiled = true
fn . valueType . args = args
fn . position = self . stackIndex
self . stackIndex = idx
return fn
2023-01-17 12:53:23 +01:00
proc generateCall ( self : BytecodeCompiler , fn : Name , args : seq [ Expression ] , line : int ) =
2022-12-15 13:22:34 +01:00
## Small wrapper that abstracts emitting a call instruction
## for a given function
2023-06-01 12:56:59 +02:00
if fn . valueType . isBuiltin :
2022-12-15 13:22:34 +01:00
self . handleBuiltinFunction ( fn . valueType , args , line )
return
case fn . kind :
of NameKind . Var :
self . identifier ( VarDecl ( fn . node ) . name )
of NameKind . Function :
self . emitByte ( LoadUInt64 , line )
self . emitBytes ( self . chunk . writeConstant ( fn . codePos . toLong ( ) ) , line )
else :
discard # Unreachable
if fn . valueType . forwarded :
self . forwarded . add ( ( fn , self . chunk . consts . high ( ) - 7 ) )
self . emitByte ( LoadUInt64 , line )
self . emitBytes ( self . chunk . writeConstant ( 0 . toLong ( ) ) , line )
let pos = self . chunk . consts . len ( ) - 8
for arg in reversed ( args ) :
self . expression ( arg )
# Creates a new call frame and jumps
# to the function's first instruction
# in the code
self . emitByte ( Call , line )
self . emitBytes ( args . len ( ) . toTriple ( ) , line )
self . patchReturnAddress ( pos )
proc specialize ( self : BytecodeCompiler , typ : Type , args : seq [ Expression ] ) : Type {. discardable . } =
2023-06-01 12:56:59 +02:00
## Instantiates a generic type
2022-12-15 13:22:34 +01:00
var mapping : TableRef [ string , Type ] = newTable [ string , Type ] ( )
var kind : Type
result = deepCopy ( typ )
case result . kind :
of TypeKind . Function :
2023-06-01 12:56:59 +02:00
# This loop checks if a user tries to reassign a generic's
2022-12-15 13:22:34 +01:00
# name to a different type
for i , ( name , typ , default ) in result . args :
if typ . kind ! = Generic :
continue
kind = self . inferOrError ( args [ i ] )
if typ . name in mapping and not self . compare ( kind , mapping [ typ . name ] ) :
self . error ( & " expecting generic argument ' {typ.name} ' to be of type {self.stringify(mapping[typ.name])}, got {self.stringify(kind)} " , args [ i ] )
mapping [ typ . name ] = kind
result . args [ i ] . kind = kind
if not result . returnType . isNil ( ) and result . returnType . kind = = Generic :
if result . returnType . name in mapping :
result . returnType = mapping [ result . returnType . name ]
2023-06-01 12:56:59 +02:00
elif mapping . len ( ) = = 0 :
# The function has no generic arguments,
# just a generic return type
var typ : Type
for i , gen in result . fun . generics :
if gen . name . token . lexeme = = result . returnType . name :
typ = result . args [ i ] . kind
break
if typ . isNil ( ) :
self . error ( & " unknown generic argument name ' {result.returnType.name} ' " , result . fun )
result . returnType = typ
2022-12-15 13:22:34 +01:00
else :
self . error ( & " unknown generic argument name ' {result.returnType.name} ' " , result . fun )
else :
discard # TODO: Custom user-defined types
proc terminateProgram ( self : BytecodeCompiler , pos : int ) =
## Utility to terminate a peon program
self . patchForwardDeclarations ( )
self . endScope ( )
2023-06-01 12:56:59 +02:00
self . emitByte ( OpCode . Return , self . peek ( ) . token . line )
self . emitByte ( 0 , self . peek ( ) . token . line ) # Entry point has no return value
self . patchReturnAddress ( pos )
2022-12-15 13:22:34 +01:00
proc beginProgram ( self : BytecodeCompiler ) : int =
## Utility to begin a peon program's
## bytecode. Returns the position of
## a dummy return address of the program's
## entry point to be patched by terminateProgram
if self . currentModule . isNil ( ) :
# We declare the program's main module
2023-01-17 12:53:23 +01:00
var mainModule = Name ( kind : NameKind . Module ,
depth : 0 ,
isPrivate : true ,
isConst : false ,
isLet : false ,
owner : nil ,
file : self . file ,
path : self . file ,
codePos : 0 ,
ident : newIdentExpr ( Token ( lexeme : self . file , kind : Identifier ) ) ,
resolved : true ,
line : 1 )
2022-12-15 13:22:34 +01:00
self . names . add ( mainModule )
self . currentModule = mainModule
# Every peon program has a hidden entry point in
# which user code is wrapped. Think of it as if
# peon is implicitly writing the main() function
# of your program and putting all of your code in
# there. While we call our entry point just like
# any regular peon function, we can't use our handy
# helper generateCall() because we need to keep track
# of where our program ends (which we don't know yet).
# To fix this, we emit dummy offsets and patch them
# later, once we know the boundaries of our hidden main()
2023-01-17 12:53:23 +01:00
var main = Name ( depth : 0 ,
2022-12-15 13:22:34 +01:00
isPrivate : true ,
isConst : false ,
isLet : false ,
owner : self . currentModule ,
file : self . file ,
valueType : Type ( kind : Function ,
returnType : nil ,
args : @ [ ] ,
) ,
codePos : self . chunk . code . len ( ) + 12 ,
ident : newIdentExpr ( Token ( lexeme : " " , kind : Identifier ) ) ,
kind : NameKind . Function ,
resolved : true ,
line : 1 )
self . names . add ( main )
self . emitByte ( LoadUInt64 , 1 )
self . emitBytes ( self . chunk . writeConstant ( main . codePos . toLong ( ) ) , 1 )
self . emitByte ( LoadUInt64 , 1 )
self . emitBytes ( self . chunk . writeConstant ( 0 . toLong ( ) ) , 1 )
result = self . chunk . consts . len ( ) - 8
self . emitByte ( Call , 1 )
self . emitBytes ( 0 . toTriple ( ) , 1 )
method literal ( self : BytecodeCompiler , node : ASTNode , compile : bool = true ) : Type {. discardable . } =
## Emits instructions for literals such
## as singletons, strings and numbers
case node . kind :
of trueExpr :
result = Type ( kind : Bool )
if compile :
self . emitByte ( LoadTrue , node . token . line )
of falseExpr :
result = Type ( kind : Bool )
if compile :
self . emitByte ( LoadFalse , node . token . line )
of strExpr :
result = Type ( kind : String )
if compile :
self . emitConstant ( LiteralExpr ( node ) , Type ( kind : String ) )
of intExpr :
let y = IntExpr ( node )
let kind = self . infer ( y )
result = kind
if kind . kind in [ Int64 , Int32 , Int16 , Int8 ] :
var x : int
try :
discard parseInt ( y . literal . lexeme , x )
except ValueError :
self . error ( " integer value out of range " )
else :
var x : uint64
try :
discard parseBiggestUInt ( y . literal . lexeme , x )
except ValueError :
self . error ( " integer value out of range " )
if compile :
self . emitConstant ( y , kind )
2022-08-30 12:55:14 +02:00
of hexExpr :
var x : int
var y = HexExpr ( node )
2022-12-02 13:35:54 +01:00
result = self . infer ( y )
2022-08-30 12:55:14 +02:00
try :
discard parseHex ( y . literal . lexeme , x )
except ValueError :
self . error ( " integer value out of range " )
let node = newIntExpr ( Token ( lexeme : $ x , line : y . token . line ,
pos : ( start : y . token . pos . start ,
2022-11-23 01:02:35 +01:00
stop : y . token . pos . start + len ( $ x ) ) ,
relPos : ( start : y . token . relPos . start , stop : y . token . relPos . start + len ( $ x ) )
)
2022-08-30 12:55:14 +02:00
)
2022-12-02 13:35:54 +01:00
if compile :
self . emitConstant ( node , result )
2022-08-30 12:55:14 +02:00
of binExpr :
var x : int
var y = BinExpr ( node )
2022-12-02 13:35:54 +01:00
result = self . infer ( y )
2022-08-30 12:55:14 +02:00
try :
discard parseBin ( y . literal . lexeme , x )
except ValueError :
self . error ( " integer value out of range " )
let node = newIntExpr ( Token ( lexeme : $ x , line : y . token . line ,
pos : ( start : y . token . pos . start ,
2022-11-23 01:02:35 +01:00
stop : y . token . pos . start + len ( $ x ) ) ,
relPos : ( start : y . token . relPos . start , stop : y . token . relPos . start + len ( $ x ) )
2022-08-30 12:55:14 +02:00
)
)
2022-12-02 13:35:54 +01:00
if compile :
self . emitConstant ( node , result )
2022-08-30 12:55:14 +02:00
of octExpr :
var x : int
var y = OctExpr ( node )
2022-12-02 13:35:54 +01:00
result = self . infer ( y )
2022-08-30 12:55:14 +02:00
try :
discard parseOct ( y . literal . lexeme , x )
except ValueError :
self . error ( " integer value out of range " )
let node = newIntExpr ( Token ( lexeme : $ x , line : y . token . line ,
pos : ( start : y . token . pos . start ,
2022-11-23 01:02:35 +01:00
stop : y . token . pos . start + len ( $ x ) ) ,
relPos : ( start : y . token . relPos . start , stop : y . token . relPos . start + len ( $ x ) )
2022-08-30 12:55:14 +02:00
)
)
2022-12-02 13:35:54 +01:00
if compile :
self . emitConstant ( node , result )
2022-08-30 12:55:14 +02:00
of floatExpr :
var x : float
var y = FloatExpr ( node )
2022-12-02 13:35:54 +01:00
result = self . infer ( y )
2022-08-30 12:55:14 +02:00
try :
discard parseFloat ( y . literal . lexeme , x )
except ValueError :
self . error ( " floating point value out of range " )
2022-12-02 13:35:54 +01:00
if compile :
self . emitConstant ( y , result )
2022-08-30 12:55:14 +02:00
of awaitExpr :
2022-12-02 13:35:54 +01:00
discard # TODO
2022-08-30 12:55:14 +02:00
else :
self . error ( & " invalid AST node of kind {node.kind} at literal(): {node} (This is an internal error and most likely a bug!) " )
2022-12-15 13:22:34 +01:00
method unary ( self : BytecodeCompiler , node : UnaryExpr , compile : bool = true ) : Type {. discardable . } =
2022-11-27 13:39:41 +01:00
## Compiles all unary expressions
var default : Expression
let fn = Type ( kind : Function ,
returnType : Type ( kind : Any ) ,
args : @ [ ( " " , self . inferOrError ( node . a ) , default ) ] )
2023-05-22 15:54:38 +02:00
var impl = self . match ( node . token . lexeme , fn , node )
2023-05-23 11:13:10 +02:00
result = impl . valueType
if impl . isGeneric :
result = self . specialize ( impl . valueType , @ [ node . a ] )
elif impl . valueType . isAuto :
2023-05-22 15:54:38 +02:00
impl = self . prepareAutoFunction ( impl , fn . args )
2023-05-23 11:13:10 +02:00
result = impl . valueType
result = result . returnType
2022-11-29 16:48:05 +01:00
if compile :
2023-01-17 12:53:23 +01:00
self . generateCall ( impl , @ [ node . a ] , impl . line )
2022-08-30 12:55:14 +02:00
2022-12-15 13:22:34 +01:00
method binary ( self : BytecodeCompiler , node : BinaryExpr , compile : bool = true ) : Type {. discardable . } =
2022-11-27 13:39:41 +01:00
## Compiles all binary expressions
var default : Expression
2022-11-28 18:21:38 +01:00
let fn = Type ( kind : Function , returnType : Type ( kind : Any ) , args : @ [ ( " " , self . inferOrError ( node . a ) , default ) , ( " " , self . inferOrError ( node . b ) , default ) ] )
2023-05-22 15:54:38 +02:00
var impl = self . match ( node . token . lexeme , fn , node )
2023-05-23 11:13:10 +02:00
result = impl . valueType
if impl . isGeneric :
result = self . specialize ( impl . valueType , @ [ node . a , node . b ] )
elif impl . valueType . isAuto :
2023-05-22 15:54:38 +02:00
impl = self . prepareAutoFunction ( impl , fn . args )
2023-05-23 11:13:10 +02:00
result = impl . valueType
result = result . returnType
2022-11-29 16:48:05 +01:00
if compile :
2023-01-17 12:53:23 +01:00
self . generateCall ( impl , @ [ node . a , node . b ] , impl . line )
2022-08-30 12:55:14 +02:00
2022-12-15 13:22:34 +01:00
method identifier ( self : BytecodeCompiler , node : IdentExpr , name : Name = nil , compile : bool = true , strict : bool = true ) : Type {. discardable . } =
2022-08-30 12:55:14 +02:00
## Compiles access to identifiers
2022-11-27 13:39:41 +01:00
var s = name
if s . isNil ( ) :
2022-12-05 12:06:24 +01:00
if strict :
2023-01-17 12:53:23 +01:00
s = self . resolveOrError ( node )
2022-12-05 12:06:24 +01:00
else :
2023-01-17 12:53:23 +01:00
s = self . resolve ( node )
2022-12-05 12:06:24 +01:00
if s . isNil ( ) and not strict :
return nil
2022-12-02 13:35:54 +01:00
result = s . valueType
2023-06-01 12:56:59 +02:00
if s . kind = = NameKind . CustomType :
# This makes it so that the type of
# a type comes out as "typevar"
result = Type ( kind : Typevar , wrapped : result )
2022-11-29 16:48:05 +01:00
if not compile :
2023-01-22 17:58:32 +01:00
return result
2022-12-04 16:54:18 +01:00
var node = s . ident
2022-11-02 12:03:14 +01:00
if s . isConst :
2022-08-30 12:55:14 +02:00
# Constants are always emitted as Load* instructions
# no matter the scope depth
2023-01-22 17:58:32 +01:00
if strict :
self . emitConstant ( VarDecl ( s . node ) . value , self . inferOrError ( node ) )
else :
self . emitConstant ( VarDecl ( s . node ) . value , self . infer ( node ) )
2022-11-27 13:39:41 +01:00
elif s . kind = = NameKind . Function :
# Functions have no runtime representation, they're just
# a location to jump to, but we pretend they aren't and
# resolve them to their address into our bytecode when
# they're referenced
self . emitByte ( LoadUInt64 , node . token . line )
2023-01-22 17:58:32 +01:00
self . emitBytes ( self . chunk . writeConstant ( s . codePos . toLong ( ) ) , node . token . line )
2023-06-01 12:56:59 +02:00
elif s . kind = = NameKind . CustomType :
# Types have no runtime representation either, but we need
# to have something on the stack to pop off (just to act as
# a placeholder)
self . emitByte ( LoadNil , node . token . line )
elif s . valueType . isBuiltin :
2022-12-01 22:04:10 +01:00
case s . ident . token . lexeme :
of " nil " :
self . emitByte ( LoadNil , node . token . line )
of " nan " :
self . emitByte ( LoadNan , node . token . line )
of " inf " :
self . emitByte ( LoadInf , node . token . line )
else :
discard # Unreachable
2022-08-30 12:55:14 +02:00
else :
2022-12-06 12:55:05 +01:00
if not s . belongsTo . isNil ( ) and s . belongsTo . valueType . fun . kind = = funDecl and FunDecl ( s . belongsTo . valueType . fun ) . isTemplate :
discard
else :
2023-03-27 19:08:58 +02:00
if s . depth > 0 :
# Loads a regular variable from the current frame
self . emitByte ( LoadVar , s . ident . token . line )
else :
2023-05-23 12:10:30 +02:00
# Loads a global variable from an absolute stack
# position
2023-03-27 19:08:58 +02:00
self . emitByte ( LoadGlobal , s . ident . token . line )
2023-05-23 12:10:30 +02:00
# No need to check for -1 here: we already did a nil check above!
self . emitBytes ( s . position . toTriple ( ) , s . ident . token . line )
2022-08-30 12:55:14 +02:00
2022-12-15 13:22:34 +01:00
method assignment ( self : BytecodeCompiler , node : ASTNode , compile : bool = true ) : Type {. discardable . } =
2022-08-30 12:55:14 +02:00
## Compiles assignment expressions
case node . kind :
of assignExpr :
let node = AssignExpr ( node )
let name = IdentExpr ( node . name )
2023-01-17 12:53:23 +01:00
var r = self . resolveOrError ( name )
2022-11-02 12:03:14 +01:00
if r . isConst :
self . error ( & " cannot assign to ' {name.token.lexeme} ' (value is a constant) " , name )
2022-08-30 12:55:14 +02:00
elif r . isLet :
2022-11-02 12:03:14 +01:00
self . error ( & " cannot reassign ' {name.token.lexeme} ' (value is immutable) " , name )
2022-11-23 09:43:22 +01:00
self . check ( node . value , r . valueType )
2022-12-05 17:09:09 +01:00
self . expression ( node . value , compile )
2022-12-04 16:54:18 +01:00
var position = r . position
2022-12-09 13:40:02 +01:00
if r . depth < self . depth and r . belongsTo ! = self . currentFunction :
2022-12-04 16:54:18 +01:00
self . warning ( WarningKind . MutateOuterScope , & " mutation of ' {r.ident.token.lexeme} ' declared in outer scope ({r.owner.file}.pn:{r.ident.token.line}:{r.ident.token.relPos.start}) " , nil , node )
result = r . valueType
2022-11-29 16:48:05 +01:00
if not compile :
return
2022-12-04 16:54:18 +01:00
self . emitByte ( StoreVar , node . token . line )
self . emitBytes ( position . toTriple ( ) , node . token . line )
2022-08-30 12:55:14 +02:00
of setItemExpr :
2022-12-02 13:35:54 +01:00
let node = SetItemExpr ( node )
let name = IdentExpr ( node . name )
var r = self . resolveOrError ( name )
if r . isConst :
self . error ( & " cannot assign to ' {name.token.lexeme} ' (value is a constant) " , name )
elif r . isLet :
self . error ( & " cannot reassign ' {name.token.lexeme} ' (value is immutable) " , name )
if r . valueType . kind ! = CustomType :
self . error ( " only types have fields " , node )
2022-08-30 12:55:14 +02:00
else :
self . error ( & " invalid AST node of kind {node.kind} at assignment(): {node} (This is an internal error and most likely a bug) " )
2022-07-09 12:47:53 +02:00
2022-04-04 12:29:23 +02:00
2023-06-01 12:56:59 +02:00
method makeConcrete ( self : BytecodeCompiler , node : GenericExpr , compile : bool = true ) : Type =
## Builds a concrete type from the given generic
## instantiation
var name = self . resolveOrError ( node . ident )
if not name . isGeneric :
self . error ( & " cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required " )
var fun = FunDecl ( name . node )
if fun . generics . len ( ) ! = node . args . len ( ) :
self . error ( & " wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()} instead) " )
var concrete = deepCopy ( name . valueType )
var types : seq [ Type ] = @ [ ]
var map = newTable [ string , Type ] ( )
for arg in node . args :
types . add ( self . inferOrError ( arg ) )
if types [ ^ 1 ] . kind ! = Typevar :
self . error ( & " expecting type name during generic instantiation, got {self.stringify(types[^1])} instead " , arg )
for ( gen , value ) in zip ( fun . generics , node . args ) :
map [ gen . name . token . lexeme ] = self . inferOrError ( value )
for i , argument in concrete . args :
if argument . kind . kind ! = Generic :
continue
elif argument . name in map :
concrete . args [ i ] . kind = map [ argument . name ]
else :
self . error ( & " unknown generic argument name ' {argument.name} ' " , concrete . fun )
if not concrete . returnType . isNil ( ) and concrete . returnType . kind = = Generic :
if concrete . returnType . name in map :
concrete . returnType = map [ concrete . returnType . name ]
else :
self . error ( & " unknown generic argument name ' {concrete.returnType.name} ' " , concrete . fun )
if compile :
# Types don't exist at runtime, but if you want to
# assign them to variables then you need *something*
# to pop off the stack, so we just push a nil
self . emitByte ( LoadNil , node . token . line )
result = concrete
2022-12-15 13:22:34 +01:00
method call ( self : BytecodeCompiler , node : CallExpr , compile : bool = true ) : Type {. discardable . } =
2023-05-22 15:54:38 +02:00
## Compiles function calls
2022-11-27 13:39:41 +01:00
var args : seq [ tuple [ name : string , kind : Type , default : Expression ] ] = @ [ ]
2022-05-30 22:06:15 +02:00
var argExpr : seq [ Expression ] = @ [ ]
2022-11-27 13:39:41 +01:00
var default : Expression
2022-05-30 09:29:03 +02:00
var kind : Type
2022-11-27 13:39:41 +01:00
for i , argument in node . arguments . positionals :
2023-03-04 12:13:19 +01:00
kind = self . infer ( argument ) # We don't use inferOrError so that we can raise a more appropriate error message later
2022-06-13 15:04:53 +02:00
if kind . isNil ( ) :
2022-11-27 13:39:41 +01:00
if argument . kind = = NodeKind . identExpr :
2022-12-04 16:54:18 +01:00
self . error ( & " reference to undefined name ' {argument.token.lexeme} ' " , argument )
2022-11-27 13:39:41 +01:00
self . error ( & " positional argument {i + 1} in function call has no type " , argument )
args . add ( ( " " , kind , default ) )
2022-05-30 22:06:15 +02:00
argExpr . add ( argument )
2022-11-27 13:39:41 +01:00
for i , argument in node . arguments . keyword :
2022-11-29 16:48:05 +01:00
kind = self . infer ( argument . value )
2022-11-27 13:39:41 +01:00
if kind . isNil ( ) :
if argument . value . kind = = NodeKind . identExpr :
2022-12-04 16:54:18 +01:00
self . error ( & " reference to undefined name ' {argument.value.token.lexeme} ' " , argument . value )
2022-11-27 13:39:41 +01:00
self . error ( & " keyword argument ' {argument.name.token.lexeme} ' in function call has no type " , argument . value )
args . add ( ( argument . name . token . lexeme , kind , default ) )
argExpr . add ( argument . value )
2022-05-30 09:29:03 +02:00
case node . callee . kind :
2022-12-04 16:54:18 +01:00
of NodeKind . identExpr :
2022-08-30 12:55:14 +02:00
# Calls like hi()
2022-12-05 12:06:24 +01:00
var impl = self . match ( IdentExpr ( node . callee ) . name . lexeme , Type ( kind : Function , returnType : Type ( kind : All ) , args : args ) , node )
2022-12-01 22:04:10 +01:00
result = impl . valueType
2023-05-23 11:13:10 +02:00
if impl . isGeneric :
result = self . specialize ( impl . valueType , argExpr )
elif impl . valueType . isAuto :
2023-01-17 12:53:23 +01:00
impl = self . prepareAutoFunction ( impl , args )
2023-03-05 16:49:14 +01:00
result = impl . valueType
if result . fun . kind = = NodeKind . lambdaExpr :
self . lambdaExpr ( LambdaExpr ( result . fun ) , compile = compile )
2023-05-22 15:54:38 +02:00
if not impl . valueType . compiled :
2023-03-05 16:49:14 +01:00
self . funDecl ( FunDecl ( result . fun ) , impl )
2022-12-02 13:35:54 +01:00
result = result . returnType
2023-06-01 12:56:59 +02:00
self . dispatchDelayedPragmas ( impl )
2022-11-29 16:48:05 +01:00
if compile :
2023-06-01 12:56:59 +02:00
# Lambdas can't be templates :P
2022-12-06 12:55:05 +01:00
if impl . valueType . fun . kind = = funDecl and FunDecl ( impl . valueType . fun ) . isTemplate :
for arg in reversed ( argExpr ) :
self . expression ( arg )
let code = BlockStmt ( FunDecl ( impl . valueType . fun ) . body ) . code
for i , decl in code :
if i < code . high ( ) :
self . declaration ( decl )
else :
# The last expression in a template
2023-03-04 12:13:19 +01:00
# is its "return value", so we compute
2022-12-06 12:55:05 +01:00
# it, but don't pop it off the stack
if decl . kind = = exprStmt :
self . expression ( ExprStmt ( decl ) . expression )
else :
self . declaration ( decl )
else :
2023-01-17 12:53:23 +01:00
self . generateCall ( impl , argExpr , node . token . line )
2022-06-08 16:07:08 +02:00
of NodeKind . callExpr :
2022-08-30 12:55:14 +02:00
# Calling a call expression, like hello()()
var node : Expression = node
var all : seq [ CallExpr ] = @ [ ]
2022-11-27 13:39:41 +01:00
# Since there can be as many consecutive calls as
# the user wants, we need to "extract" all of them
2022-08-30 12:55:14 +02:00
while CallExpr ( node ) . callee . kind = = callExpr :
all . add ( CallExpr ( CallExpr ( node ) . callee ) )
node = CallExpr ( node ) . callee
2022-11-27 13:39:41 +01:00
# Now that we know how many call expressions we
# need to compile, we start from the outermost
2022-12-02 13:35:54 +01:00
# one and work our way to the innermost call
for exp in all :
result = self . call ( exp , compile )
if compile and result . kind = = Function :
self . generateCall ( result , argExpr , node . token . line )
result = result . returnType
2022-12-04 16:54:18 +01:00
of NodeKind . getItemExpr :
2023-06-01 12:56:59 +02:00
let node = GetItemExpr ( node . callee )
2023-05-23 11:13:10 +02:00
result = self . getItemExpr ( node , compile = false , matching = Type ( kind : Function , args : args , returnType : Type ( kind : All ) ) )
2023-05-22 15:54:38 +02:00
var fn : Name
2023-05-23 11:13:10 +02:00
# getItemExpr returns a Type object, but
# we need a Name object!
2023-05-22 15:54:38 +02:00
for name in self . names :
2023-05-23 11:13:10 +02:00
if name . valueType = = result :
2023-05-22 15:54:38 +02:00
fn = name
break
2023-05-23 11:13:10 +02:00
if fn . isGeneric :
result = self . specialize ( result , argExpr )
elif result . isAuto :
fn = self . prepareAutoFunction ( fn , args )
result = fn . valueType
result = result . returnType
2023-05-22 15:54:38 +02:00
if compile :
self . generateCall ( fn , argExpr , node . token . line )
2023-03-04 12:13:19 +01:00
of NodeKind . lambdaExpr :
2023-06-01 12:56:59 +02:00
# Calling a lambda
2023-03-04 12:13:19 +01:00
var node = LambdaExpr ( node . callee )
2023-06-01 12:56:59 +02:00
let impl = self . lambdaExpr ( node , compile = compile )
2023-03-07 11:41:37 +01:00
result = impl . returnType
2023-03-04 12:13:19 +01:00
if compile :
self . generateCall ( impl , argExpr , node . token . line )
2023-06-01 12:56:59 +02:00
of NodeKind . genericExpr :
# Instantiating a generic type
let node = GenericExpr ( node . callee )
let concrete = self . makeConcrete ( node )
var impl = self . resolve ( node . ident ) . deepCopy ( )
impl . valueType = concrete
result = impl . valueType . returnType
if compile :
self . generateCall ( impl , argExpr , node . token . line )
2022-05-30 09:29:03 +02:00
else :
2022-10-11 09:56:55 +02:00
let typ = self . infer ( node )
2022-06-21 20:18:53 +02:00
if typ . isNil ( ) :
2022-12-02 13:35:54 +01:00
self . error ( & " expression has no type " , node )
2022-06-21 20:18:53 +02:00
else :
2022-12-02 13:35:54 +01:00
self . error ( & " object of type ' {self.stringify(typ)} ' is not callable " , node )
2023-06-01 12:56:59 +02:00
if not result . isNil ( ) and result . kind = = Typevar :
result = result . wrapped
2022-05-30 09:29:03 +02:00
2022-12-15 13:22:34 +01:00
method getItemExpr ( self : BytecodeCompiler , node : GetItemExpr , compile : bool = true , matching : Type = nil ) : Type {. discardable . } =
2022-11-27 13:39:41 +01:00
## Compiles accessing to fields of a type or
## module namespace. If the compile flag is set
## to false, no code is generated for resolving
## the attribute. Returns the type of the object
## that is resolved
case node . obj . kind :
of identExpr :
let name = self . resolveOrError ( IdentExpr ( node . obj ) )
case name . kind :
of NameKind . Module :
2022-12-04 16:54:18 +01:00
var values = self . findInModule ( node . name . token . lexeme , name )
if len ( values ) = = 0 :
2022-11-27 13:39:41 +01:00
self . error ( & " reference to undefined name ' {node.name.token.lexeme} ' in module ' {name.ident.token.lexeme} ' " )
2022-12-04 16:54:18 +01:00
elif len ( values ) > 1 and matching . isNil ( ) :
self . error ( & " ambiguous reference for ' {node.name.token.lexeme} ' in module ' {name.ident.token.lexeme} ' " )
if not matching . isNil ( ) :
for name in values :
if self . compare ( name . valueType , matching ) :
result = name . valueType
2023-05-23 11:13:10 +02:00
return
2022-12-04 16:54:18 +01:00
if len ( values ) = = 1 :
result = values [ 0 ] . valueType
else :
self . error ( & " ambiguous reference for ' {node.name.token.lexeme} ' in module ' {name.ident.token.lexeme} ' " )
2022-11-27 13:39:41 +01:00
if compile :
2023-01-17 12:53:23 +01:00
self . identifier ( nil , values [ 0 ] )
2022-11-27 13:39:41 +01:00
else :
self . error ( " invalid syntax " , node . obj )
else :
self . error ( " invalid syntax " , node )
2023-03-04 12:13:19 +01:00
proc blockStmt ( self : BytecodeCompiler , node : BlockStmt , compile : bool = true ) =
## Compiles block statements, which create
## a new local scope
self . beginScope ( )
var last : Declaration
for decl in node . code :
if not last . isNil ( ) :
case last . kind :
of breakStmt , continueStmt :
self . warning ( UnreachableCode , & " code after ' {last.token.lexeme} ' statement is unreachable " , nil , decl )
else :
discard
self . declaration ( decl )
last = decl
self . endScope ( )
2022-12-15 13:22:34 +01:00
method lambdaExpr ( self : BytecodeCompiler , node : LambdaExpr , compile : bool = true ) : Type {. discardable . } =
2022-12-02 13:35:54 +01:00
## Compiles lambda functions as expressions
2023-03-05 16:49:14 +01:00
result = Type ( kind : Function , isLambda : true , fun : node , location : 0 , compiled : true )
2023-03-04 12:13:19 +01:00
let function = self . currentFunction
2022-12-02 13:35:54 +01:00
var default : Expression
2023-01-17 12:53:23 +01:00
var name : Name
2022-12-02 13:35:54 +01:00
var i = 0
2023-03-05 16:49:14 +01:00
let stackIdx = self . stackIndex
self . stackIndex = 2
2022-12-02 13:35:54 +01:00
for argument in node . arguments :
if self . names . high ( ) > 16777215 :
self . error ( " cannot declare more than 16777215 variables at a time " )
2023-03-05 16:49:14 +01:00
name = Name ( depth : self . depth + 1 ,
2022-12-02 13:35:54 +01:00
isPrivate : true ,
owner : self . currentModule ,
file : self . currentModule . file ,
isConst : false ,
ident : argument . name ,
valueType : self . inferOrError ( argument . valueType ) ,
codePos : 0 ,
isLet : false ,
line : argument . name . token . line ,
belongsTo : nil , # TODO
kind : NameKind . Argument ,
2023-03-05 16:49:14 +01:00
node : argument . name ,
position : self . stackIndex
2022-12-06 10:59:05 +01:00
)
2023-03-07 11:41:37 +01:00
if name . valueType . kind = = Auto :
self . error ( " due to current compiler limitations, automatic types cannot be used in lambdas " , name . ident )
2022-12-06 10:59:05 +01:00
if compile :
self . names . add ( name )
2023-03-05 16:49:14 +01:00
inc ( self . stackIndex )
2022-12-02 13:35:54 +01:00
if node . arguments . high ( ) - node . defaults . high ( ) < = node . arguments . high ( ) :
# There's a default argument!
2022-12-06 10:59:05 +01:00
result . args . add ( ( name . ident . token . lexeme , name . valueType , node . defaults [ i ] ) )
2022-12-02 13:35:54 +01:00
inc ( i )
else :
# This argument has no default
2022-12-06 10:59:05 +01:00
result . args . add ( ( name . ident . token . lexeme , name . valueType , default ) )
2022-12-02 13:35:54 +01:00
# The function needs a return type too!
if not node . returnType . isNil ( ) :
result . returnType = self . inferOrError ( node . returnType )
2023-03-04 12:13:19 +01:00
self . currentFunction = Name ( depth : self . depth ,
isPrivate : true ,
isConst : false ,
owner : self . currentModule ,
file : self . file ,
2023-03-05 16:49:14 +01:00
valueType : result ,
2023-03-04 12:13:19 +01:00
ident : nil ,
node : node ,
isLet : false ,
line : node . token . line ,
kind : NameKind . Function ,
2023-03-05 16:49:14 +01:00
belongsTo : function ,
2023-05-22 12:57:38 +02:00
isReal : true ,
)
if compile and node notin self . lambdas and not node . body . isNil ( ) :
2023-03-05 16:49:14 +01:00
self . lambdas . add ( node )
2023-03-04 12:13:19 +01:00
let jmp = self . emitJump ( JumpForwards , node . token . line )
if BlockStmt ( node . body ) . code . len ( ) = = 0 :
self . error ( " cannot construct lambda with empty body " )
var last : Declaration
self . beginScope ( )
result . location = self . chunk . code . len ( )
for decl in BlockStmt ( node . body ) . code :
if not last . isNil ( ) :
if last . kind = = returnStmt :
self . warning ( UnreachableCode , " code after ' return ' statement is unreachable " , nil , decl )
self . declaration ( decl )
last = decl
let typ = self . currentFunction . valueType . returnType
var hasVal : bool = false
case self . currentFunction . valueType . fun . kind :
of NodeKind . funDecl :
hasVal = FunDecl ( self . currentFunction . valueType . fun ) . hasExplicitReturn
of NodeKind . lambdaExpr :
hasVal = LambdaExpr ( self . currentFunction . valueType . fun ) . hasExplicitReturn
else :
discard # Unreachable
if not hasVal and not typ . isNil ( ) :
# There is no explicit return statement anywhere in the function's
# body: while this is not a tremendously useful piece of information
# (since the presence of at least one doesn't mean all control flow
# cases are covered), it definitely is an error worth reporting
self . error ( " function has an explicit return type, but no return statement was found " , node )
hasVal = hasVal and not typ . isNil ( )
for jump in self . currentFunction . valueType . retJumps :
self . patchJump ( jump )
# Terminates the function's context
self . emitByte ( OpCode . Return , self . peek ( ) . token . line )
if hasVal :
self . emitByte ( 1 , self . peek ( ) . token . line )
else :
self . emitByte ( 0 , self . peek ( ) . token . line )
# Well, we've compiled everything: time to patch
# the jump offset
self . patchJump ( jmp )
self . emitByte ( LoadUInt64 , node . token . line )
self . emitBytes ( self . chunk . writeConstant ( result . location . toLong ( ) ) , node . token . line )
2023-03-05 16:49:14 +01:00
self . endScope ( )
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
self . currentFunction = function
self . stackIndex = stackIdx
2022-12-02 13:35:54 +01:00
2022-12-15 13:22:34 +01:00
method expression ( self : BytecodeCompiler , node : Expression , compile : bool = true ) : Type {. discardable . } =
2022-04-04 12:29:23 +02:00
## Compiles all expressions
case node . kind :
2023-06-01 12:56:59 +02:00
of NodeKind . genericExpr :
return self . makeConcrete ( GenericExpr ( node ) )
2022-05-30 22:06:15 +02:00
of NodeKind . callExpr :
2022-12-01 22:04:10 +01:00
return self . call ( CallExpr ( node ) , compile )
2022-11-27 13:39:41 +01:00
of NodeKind . getItemExpr :
2022-12-01 22:04:10 +01:00
return self . getItemExpr ( GetItemExpr ( node ) , compile )
2022-11-27 13:39:41 +01:00
of NodeKind . pragmaExpr :
discard # TODO
2022-04-04 12:29:23 +02:00
# Note that for setItem and assign we don't convert
# the node to its true type because that type information
# would be lost in the call anyway. The differentiation
2022-05-07 10:48:01 +02:00
# happens in self.assignment()
2022-12-02 13:35:54 +01:00
of NodeKind . setItemExpr , NodeKind . assignExpr :
2022-12-04 16:54:18 +01:00
return self . assignment ( node , compile )
2022-12-02 13:35:54 +01:00
of NodeKind . identExpr :
return self . identifier ( IdentExpr ( node ) , compile = compile )
of NodeKind . unaryExpr :
2022-04-04 12:29:23 +02:00
# Unary expressions such as ~5 and -3
2022-12-02 13:35:54 +01:00
return self . unary ( UnaryExpr ( node ) , compile )
of NodeKind . groupingExpr :
2022-04-04 12:29:23 +02:00
# Grouping expressions like (2 + 1)
2022-12-01 22:04:10 +01:00
return self . expression ( GroupingExpr ( node ) . expression , compile )
2022-12-02 13:35:54 +01:00
of NodeKind . binaryExpr :
2022-04-04 12:29:23 +02:00
# Binary expressions such as 2 ^ 5 and 0.66 * 3.14
2022-12-02 13:35:54 +01:00
return self . binary ( BinaryExpr ( node ) )
of NodeKind . intExpr , NodeKind . hexExpr , NodeKind . binExpr , NodeKind . octExpr ,
NodeKind . strExpr , NodeKind . falseExpr , NodeKind . trueExpr , NodeKind . floatExpr :
2022-05-07 10:48:01 +02:00
# Since all of these AST nodes share the
# same overall structure and the kind
# field is enough to tell one from the
2022-05-18 13:32:32 +02:00
# other, why bother with specialized
2022-05-07 10:48:01 +02:00
# cases when one is enough?
2022-12-02 13:35:54 +01:00
return self . literal ( node , compile )
of NodeKind . lambdaExpr :
return self . lambdaExpr ( LambdaExpr ( node ) , compile )
2022-04-04 12:29:23 +02:00
else :
self . error ( & " invalid AST node of kind {node.kind} at expression(): {node} (This is an internal error and most likely a bug) " )
2022-12-15 13:22:34 +01:00
proc ifStmt ( self : BytecodeCompiler , node : IfStmt ) =
## Compiles if/else statements for conditional
## execution of code
self . check ( node . condition , Type ( kind : Bool ) )
self . expression ( node . condition )
let jump = self . emitJump ( JumpIfFalsePop , node . token . line )
self . statement ( node . thenBranch )
let jump2 = self . emitJump ( JumpForwards , node . token . line )
self . patchJump ( jump )
if not node . elseBranch . isNil ( ) :
self . statement ( node . elseBranch )
self . patchJump ( jump2 )
proc whileStmt ( self : BytecodeCompiler , node : WhileStmt ) =
## Compiles C-style while loops and
## desugared C-style for loops
self . check ( node . condition , Type ( kind : Bool ) )
let start = self . chunk . code . high ( )
self . expression ( node . condition )
let jump = self . emitJump ( JumpIfFalsePop , node . token . line )
self . statement ( node . body )
self . emitLoop ( start , node . token . line )
self . patchJump ( jump )
2022-11-27 13:39:41 +01:00
# TODO
2022-12-15 11:48:36 +01:00
proc awaitStmt ( self : BytecodeCompiler , node : AwaitStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles await statements
2022-04-04 12:29:23 +02:00
2022-11-27 13:39:41 +01:00
# TODO
2022-12-15 11:48:36 +01:00
proc deferStmt ( self : BytecodeCompiler , node : DeferStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles defer statements
# TODO
2022-12-15 11:48:36 +01:00
proc yieldStmt ( self : BytecodeCompiler , node : YieldStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles yield statements
# TODO
2022-12-15 11:48:36 +01:00
proc raiseStmt ( self : BytecodeCompiler , node : RaiseStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles raise statements
# TODO
2022-12-15 11:48:36 +01:00
proc assertStmt ( self : BytecodeCompiler , node : AssertStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles assert statements
# TODO
# TODO
2022-12-15 11:48:36 +01:00
proc forEachStmt ( self : BytecodeCompiler , node : ForEachStmt ) =
2022-12-01 22:04:10 +01:00
## Compiles foreach loops
2022-06-02 01:33:56 +02:00
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc returnStmt ( self : BytecodeCompiler , node : ReturnStmt ) =
2022-06-21 20:18:53 +02:00
## Compiles return statements
2022-11-28 19:03:08 +01:00
if self . currentFunction . valueType . returnType . isNil ( ) and not node . value . isNil ( ) :
self . error ( " cannot return a value from a void function " , node . value )
elif not self . currentFunction . valueType . returnType . isNil ( ) and node . value . isNil ( ) :
self . error ( " bare return statement is only allowed in void functions " , node )
2022-06-07 11:23:08 +02:00
if not node . value . isNil ( ) :
2022-12-05 17:09:09 +01:00
if self . currentFunction . valueType . returnType . kind = = Auto :
self . currentFunction . valueType . returnType = self . inferOrError ( node . value )
self . check ( node . value , self . currentFunction . valueType . returnType )
self . expression ( node . value )
self . emitByte ( OpCode . SetResult , node . token . line )
2022-10-13 13:12:24 +02:00
# Since the "set result" part and "exit the function" part
# of our return mechanism are already decoupled into two
# separate opcodes, we perform the former and then jump to
# the function's last return statement, which is always emitted
# by funDecl() at the end of the function's lifecycle, greatly
2022-11-02 12:03:14 +01:00
# simplifying the design, since now there's just one return
2022-10-13 13:12:24 +02:00
# instruction to jump to instead of many potential points
2022-11-02 12:03:14 +01:00
# where the function returns from. Note that depending on whether
# the function has any local variables or not, this jump might be
# patched to jump to the function's PopN/PopC instruction(s) rather
# than straight to the return statement
2022-12-05 17:09:09 +01:00
self . currentFunction . valueType . retJumps . add ( self . emitJump ( JumpForwards , node . token . line ) )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc continueStmt ( self : BytecodeCompiler , node : ContinueStmt , compile : bool = true ) =
2023-01-05 12:44:11 +01:00
## Compiles continue statements. A continue statement can be
## used to jump to the beginning of a loop or block
2022-12-05 08:07:37 +01:00
if node . label . isNil ( ) :
if self . currentLoop . start > 16777215 :
self . error ( " too much code to jump over in continue statement " )
2022-12-05 12:06:24 +01:00
if compile :
self . emitByte ( Jump , node . token . line )
self . emitBytes ( self . currentLoop . start . toTriple ( ) , node . token . line )
2022-12-05 08:07:37 +01:00
else :
var blocks : seq [ NamedBlock ] = @ [ ]
var found : bool = false
for blk in reversed ( self . namedBlocks ) :
blocks . add ( blk )
if blk . name = = node . label . token . lexeme :
found = true
break
if not found :
self . error ( & " unknown block name ' {node.label.token.lexeme} ' " , node . label )
2022-12-05 12:06:24 +01:00
if compile :
self . emitByte ( Jump , node . token . line )
self . emitBytes ( blocks [ ^ 1 ] . start . toTriple ( ) , node . token . line )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc importStmt ( self : BytecodeCompiler , node : ImportStmt , compile : bool = true ) =
2023-01-05 12:44:11 +01:00
## Imports a module. This creates a new "virtual"
## (i.e simulated) module namespace and injects all
## of the module's public names into the current module
2022-11-29 16:48:05 +01:00
self . declare ( node )
2022-11-27 13:39:41 +01:00
var module = self . names [ ^ 1 ]
2022-08-14 18:37:06 +02:00
try :
2022-12-05 12:06:24 +01:00
if compile :
2023-01-17 12:53:23 +01:00
self . compileModule ( module )
2022-12-05 12:06:24 +01:00
# Importing a module automatically exports
# its public names to us
for name in self . findInModule ( " " , module ) :
2023-05-23 11:13:10 +02:00
name . exportedTo . add ( self . currentModule )
2023-05-22 15:54:38 +02:00
# We also need to export public names from other modules
# that we have explicitly exported because imports are
# compiled only once
2023-05-22 14:38:03 +02:00
for module in self . modules . values ( ) :
2023-05-23 11:13:10 +02:00
if self . currentModule in module . exportedTo :
2023-05-22 14:38:03 +02:00
for name in self . findInModule ( " " , module ) :
2023-05-23 11:13:10 +02:00
name . exportedTo . add ( self . currentModule )
2022-08-14 18:37:06 +02:00
except IOError :
2022-11-27 13:39:41 +01:00
self . error ( & " could not import ' {module.ident.token.lexeme} ' : {getCurrentExceptionMsg()} " )
2022-08-15 11:46:24 +02:00
except OSError :
2022-11-27 13:39:41 +01:00
self . error ( & " could not import ' {module.ident.token.lexeme} ' : {getCurrentExceptionMsg()} [errno {osLastError()}] " )
2022-06-07 11:23:08 +02:00
2022-12-15 11:48:36 +01:00
proc exportStmt ( self : BytecodeCompiler , node : ExportStmt , compile : bool = true ) =
2022-10-17 11:28:00 +02:00
## Exports a name at compile time to
2023-01-05 12:44:11 +01:00
## all modules importing us. The user
## needs to explicitly tell the compiler
## which of the names it imported, if any,
## should be made available to other modules
## importing it in order to avoid namespace
## pollution
2022-11-02 12:03:14 +01:00
var name = self . resolveOrError ( node . name )
2022-10-17 11:28:00 +02:00
if name . isPrivate :
self . error ( " cannot export private names " )
2023-05-23 11:13:10 +02:00
name . exportedTo . add ( self . parentModule )
2022-10-17 11:28:00 +02:00
case name . kind :
of NameKind . Module :
# We need to export everything
# this module defines!
2022-11-27 13:39:41 +01:00
for name in self . findInModule ( " " , name ) :
2023-05-23 11:13:10 +02:00
name . exportedTo . add ( self . parentModule )
2022-10-17 11:28:00 +02:00
of NameKind . Function :
2023-01-24 12:19:06 +01:00
# Only exporting a single function (or, well
# all of its implementations)
2022-10-17 11:28:00 +02:00
for name in self . findByName ( name . ident . token . lexeme ) :
if name . kind ! = NameKind . Function :
continue
2023-05-23 11:13:10 +02:00
name . exportedTo . add ( self . parentModule )
2022-10-17 11:28:00 +02:00
else :
2023-05-22 12:57:38 +02:00
self . error ( " unsupported export type " )
2022-10-17 11:28:00 +02:00
2022-12-15 11:48:36 +01:00
proc breakStmt ( self : BytecodeCompiler , node : BreakStmt ) =
2023-01-05 12:44:11 +01:00
## Compiles break statements. A break statement is used
## to jump at the end of a loop or outside of a given
## block
2022-12-05 08:47:14 +01:00
if node . label . isNil ( ) :
2023-01-24 12:19:06 +01:00
# Jumping out of a loop
2022-12-05 08:47:14 +01:00
self . currentLoop . breakJumps . add ( self . emitJump ( OpCode . JumpForwards , node . token . line ) )
if self . currentLoop . depth > self . depth :
# Breaking out of a loop closes its scope
self . endScope ( )
else :
2023-01-24 12:19:06 +01:00
# Jumping out of a block
2022-12-05 08:47:14 +01:00
var blocks : seq [ NamedBlock ] = @ [ ]
var found : bool = false
for blk in reversed ( self . namedBlocks ) :
blocks . add ( blk )
if blk . name = = node . label . token . lexeme :
for blk in blocks :
blk . broken = true
found = true
break
if not found :
self . error ( & " unknown block name ' {node.label.token.lexeme} ' " , node . label )
2022-12-15 11:48:36 +01:00
proc namedBlock ( self : BytecodeCompiler , node : NamedBlockStmt ) =
2022-12-05 08:07:37 +01:00
## Compiles named blocks
2023-01-05 12:44:11 +01:00
self . namedBlocks . add ( NamedBlock ( start : self . chunk . code . len ( ) , # Creates a new block entry
depth : self . depth ,
breakJumps : @ [ ] ,
name : NamedBlockStmt ( node ) . name . token . lexeme ) )
2022-12-05 08:07:37 +01:00
self . beginScope ( )
2022-12-05 08:47:14 +01:00
var blk = self . namedBlocks [ ^ 1 ]
2022-12-05 08:07:37 +01:00
var last : Declaration
for decl in node . code :
if not last . isNil ( ) :
case last . kind :
of NodeKind . breakStmt , NodeKind . continueStmt :
2022-12-09 13:40:02 +01:00
self . warning ( UnreachableCode , & " code after ' {last.token.lexeme} ' statement is unreachable " , nil , decl )
2022-12-05 08:07:37 +01:00
else :
discard
2022-12-05 17:09:09 +01:00
if blk . broken :
2022-12-05 08:47:14 +01:00
blk . breakJumps . add ( self . emitJump ( OpCode . JumpForwards , node . token . line ) )
2022-12-05 17:09:09 +01:00
self . declaration ( decl )
2022-12-05 08:07:37 +01:00
last = decl
2022-12-05 17:09:09 +01:00
self . patchBreaks ( )
2022-12-05 08:07:37 +01:00
self . endScope ( )
2023-01-05 12:44:11 +01:00
discard self . namedBlocks . pop ( )
2022-12-05 08:07:37 +01:00
2022-12-15 11:48:36 +01:00
proc switchStmt ( self : BytecodeCompiler , node : SwitchStmt ) =
2023-06-01 12:56:59 +02:00
## Compiles switch statements
2022-12-07 09:15:29 +01:00
self . expression ( node . switch )
2023-01-05 12:44:11 +01:00
let typeOfA = self . inferOrError ( node . switch )
2022-12-07 09:15:29 +01:00
var ifJump : int = - 1
var thenJumps : seq [ int ] = @ [ ]
2023-01-05 12:44:11 +01:00
var fn : Type
2023-01-17 12:53:23 +01:00
var impl : Name
2023-01-05 12:44:11 +01:00
var default : Expression
2023-01-24 12:19:06 +01:00
# Note that, unlike C switch statements, we don't
# cascade to other branches once the first one matches
2022-12-07 09:15:29 +01:00
for branch in node . branches :
2023-01-24 12:19:06 +01:00
# We duplicate the top of the stack so we can safely
# pop the topmost expression without losing its value
# for later comparisons
2022-12-07 09:15:29 +01:00
self . emitByte ( DupTop , branch . body . token . line )
self . expression ( branch . cond )
2023-01-24 12:19:06 +01:00
# We look for a matching equality implementation
2023-01-05 12:44:11 +01:00
fn = Type ( kind : Function , returnType : Type ( kind : Bool ) , args : @ [ ( " " , typeOfA , default ) , ( " " , self . inferOrError ( branch . cond ) , default ) ] )
2023-01-17 12:53:23 +01:00
impl = self . match ( " == " , fn , node )
2023-01-05 12:44:11 +01:00
self . generateCall ( impl , @ [ node . switch , branch . cond ] , impl . line )
2022-12-07 09:15:29 +01:00
ifJump = self . emitJump ( JumpIfFalsePop , branch . body . token . line )
self . blockStmt ( branch . body )
thenJumps . add ( self . emitJump ( JumpForwards , branch . body . token . line ) )
self . patchJump ( ifJump )
if not node . default . isNil ( ) :
self . blockStmt ( node . default )
for jump in thenJumps :
self . patchJump ( jump )
self . emitByte ( OpCode . Pop , node . token . line )
2022-12-15 11:48:36 +01:00
proc statement ( self : BytecodeCompiler , node : Statement ) =
2022-04-04 12:29:23 +02:00
## Compiles all statements
case node . kind :
of exprStmt :
2023-01-05 12:44:11 +01:00
# An expression statement is just a statement
# followed by a statement terminator (semicolon)
2022-08-17 17:31:15 +02:00
let expression = ExprStmt ( node ) . expression
2022-10-11 09:56:55 +02:00
let kind = self . infer ( expression )
2022-12-05 17:09:09 +01:00
self . expression ( expression )
2022-08-17 17:31:15 +02:00
if kind . isNil ( ) :
# The expression has no type and produces no value,
# so we don't have to pop anything
2022-06-02 01:33:56 +02:00
discard
2022-12-05 17:09:09 +01:00
elif self . replMode :
2022-10-13 16:52:37 +02:00
self . printRepl ( kind , expression )
2022-12-05 17:09:09 +01:00
else :
2022-08-30 12:55:14 +02:00
self . emitByte ( Pop , node . token . line )
2022-12-07 09:15:29 +01:00
of NodeKind . switchStmt :
self . switchStmt ( SwitchStmt ( node ) )
2022-12-05 08:07:37 +01:00
of NodeKind . namedBlockStmt :
2023-01-05 12:44:11 +01:00
self . namedBlock ( NamedBlockStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . ifStmt :
2022-12-05 17:09:09 +01:00
self . ifStmt ( IfStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . assertStmt :
2022-12-05 17:09:09 +01:00
self . assertStmt ( AssertStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . raiseStmt :
2022-12-05 17:09:09 +01:00
self . raiseStmt ( RaiseStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . breakStmt :
2022-12-05 17:09:09 +01:00
self . breakStmt ( BreakStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . continueStmt :
2022-12-05 17:09:09 +01:00
self . continueStmt ( ContinueStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . returnStmt :
2022-12-05 17:09:09 +01:00
self . returnStmt ( ReturnStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . importStmt :
2022-12-05 17:09:09 +01:00
self . importStmt ( ImportStmt ( node ) )
2022-10-17 11:28:00 +02:00
of NodeKind . exportStmt :
2022-12-05 17:09:09 +01:00
self . exportStmt ( ExportStmt ( node ) )
2022-07-09 13:36:21 +02:00
of NodeKind . whileStmt :
2022-04-04 12:29:23 +02:00
let loop = self . currentLoop
self . currentLoop = Loop ( start : self . chunk . code . len ( ) ,
2022-11-02 13:16:43 +01:00
depth : self . depth , breakJumps : @ [ ] )
2022-12-05 17:09:09 +01:00
self . whileStmt ( WhileStmt ( node ) )
self . patchBreaks ( )
2022-04-04 12:29:23 +02:00
self . currentLoop = loop
of NodeKind . forEachStmt :
2022-12-05 17:09:09 +01:00
self . forEachStmt ( ForEachStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . blockStmt :
2022-12-05 17:09:09 +01:00
self . blockStmt ( BlockStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . yieldStmt :
2022-12-05 17:09:09 +01:00
self . yieldStmt ( YieldStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . awaitStmt :
2022-12-05 17:09:09 +01:00
self . awaitStmt ( AwaitStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . deferStmt :
2022-12-05 17:09:09 +01:00
self . deferStmt ( DeferStmt ( node ) )
2022-04-04 12:29:23 +02:00
of NodeKind . tryStmt :
discard
else :
2022-05-04 14:27:15 +02:00
self . expression ( Expression ( node ) )
2022-04-04 12:29:23 +02:00
2023-05-01 15:30:36 +02:00
proc varDecl ( self : BytecodeCompiler , node : VarDecl ) =
2022-04-12 12:18:25 +02:00
## Compiles variable declarations
2023-01-05 12:44:11 +01:00
var typ : Type
2022-11-02 12:03:14 +01:00
# Our parser guarantees that the variable declaration
# will have a type declaration or a value (or both)
if node . value . isNil ( ) :
# Variable has no value: the type declaration
2022-11-02 13:16:43 +01:00
# takes over
2022-12-05 12:06:24 +01:00
if typ . kind = = Auto :
self . error ( " automatic types require initialization " , node )
2023-06-01 12:56:59 +02:00
typ = self . inferOrError ( node . valueType )
if typ . kind ! = Typevar :
self . error ( & " expecting type name, got value of type {self.stringify(typ)} instead " , node . name )
2023-01-22 17:58:32 +01:00
elif node . valueType . isNil ( ) :
2022-11-02 12:03:14 +01:00
# Variable has no type declaration: the type
# of its value takes over
typ = self . inferOrError ( node . value )
else :
# Variable has both a type declaration and
# a value: the value's type must match the
# type declaration
let expected = self . inferOrError ( node . valueType )
2022-12-05 12:06:24 +01:00
if expected . kind ! = Auto :
self . check ( node . value , expected )
# If this doesn't fail, then we're good
typ = expected
else :
2023-01-22 18:02:31 +01:00
# Let the compiler infer the type (this
# is the default behavior already, but
# some users may prefer to be explicit!)
2023-06-01 12:56:59 +02:00
typ = self . inferOrError ( node . value )
2022-04-12 12:18:25 +02:00
self . expression ( node . value )
2022-11-27 13:39:41 +01:00
self . emitByte ( AddVar , node . token . line )
2022-12-04 16:54:18 +01:00
inc ( self . stackIndex )
2023-01-22 17:58:32 +01:00
# We declare the name only now in order to make
# sure that stuff like var n = n; works as expected.
# If we declared it early, we'd have a duplicate with
# no type that would shadow the original value, which
# is no good
var name = self . declare ( node )
2022-12-04 16:54:18 +01:00
name . position = self . stackIndex
2022-11-05 14:27:57 +01:00
name . valueType = typ
2022-06-07 11:23:08 +02:00
2023-01-17 12:53:23 +01:00
proc funDecl ( self : BytecodeCompiler , node : FunDecl , name : Name ) =
2022-04-04 12:29:23 +02:00
## Compiles function declarations
2022-11-27 13:39:41 +01:00
if node . token . kind = = Operator and node . name . token . lexeme in [ " . " , " = " ] :
self . error ( & " Due to compiler limitations, the ' {node.name.token.lexeme} ' operator cannot be currently overridden " , node . name )
2022-06-21 20:18:53 +02:00
var node = node
2022-10-13 13:12:24 +02:00
var jmp : int
2023-01-05 12:44:11 +01:00
# We store the current function to restore
# it later
2022-12-04 16:54:18 +01:00
let function = self . currentFunction
2022-11-27 13:39:41 +01:00
if node . body . isNil ( ) :
2023-01-05 12:44:11 +01:00
# When we stumble across a forward declaration,
# we record it for later so we can look it up at
# the end of the module
2022-11-27 13:39:41 +01:00
self . forwarded . add ( ( name , 0 ) )
name . valueType . forwarded = true
return
2023-06-01 12:56:59 +02:00
if name . valueType . isBuiltin :
2023-05-23 12:10:30 +02:00
# Builtins are handled at call time
2022-11-27 13:39:41 +01:00
return
2023-05-23 12:10:30 +02:00
self . currentFunction = name
2022-12-04 16:54:18 +01:00
let stackIdx = self . stackIndex
self . stackIndex = name . position
2022-12-06 12:55:05 +01:00
if not node . isTemplate :
# A function's code is just compiled linearly
# and then jumped over
name . valueType . compiled = true
jmp = self . emitJump ( JumpForwards , node . token . line )
name . codePos = self . chunk . code . len ( )
name . valueType . location = name . codePos
# We let our debugger know this function's boundaries
self . chunk . functions . add ( self . chunk . code . len ( ) . toTriple ( ) )
self . functions . add ( ( start : self . chunk . code . len ( ) , stop : 0 , pos : self . chunk . functions . len ( ) - 3 , fn : name ) )
var offset = self . functions [ ^ 1 ]
var idx = self . chunk . functions . len ( )
self . chunk . functions . add ( 0 . toTriple ( ) ) # Patched it later
self . chunk . functions . add ( uint8 ( node . arguments . len ( ) ) )
if not node . name . isNil ( ) :
self . chunk . functions . add ( name . ident . token . lexeme . len ( ) . toDouble ( ) )
var s = name . ident . token . lexeme
if s . len ( ) > = uint16 . high ( ) . int :
s = node . name . token . lexeme [ 0 .. uint16 . high ( ) ]
self . chunk . functions . add ( s . toBytes ( ) )
2022-06-21 20:18:53 +02:00
else :
2022-12-06 12:55:05 +01:00
self . chunk . functions . add ( 0 . toDouble ( ) )
if BlockStmt ( node . body ) . code . len ( ) = = 0 :
self . error ( " cannot declare function with empty body " )
var last : Declaration
self . beginScope ( )
for decl in BlockStmt ( node . body ) . code :
if not last . isNil ( ) :
if last . kind = = returnStmt :
2022-12-09 13:40:02 +01:00
self . warning ( UnreachableCode , " code after ' return ' statement is unreachable " , nil , decl )
2022-12-06 12:55:05 +01:00
self . declaration ( decl )
last = decl
let typ = self . currentFunction . valueType . returnType
var hasVal : bool = false
case self . currentFunction . valueType . fun . kind :
of NodeKind . funDecl :
hasVal = FunDecl ( self . currentFunction . valueType . fun ) . hasExplicitReturn
of NodeKind . lambdaExpr :
hasVal = LambdaExpr ( self . currentFunction . valueType . fun ) . hasExplicitReturn
else :
discard # Unreachable
if not hasVal and not typ . isNil ( ) :
# There is no explicit return statement anywhere in the function's
# body: while this is not a tremendously useful piece of information
# (since the presence of at least one doesn't mean all control flow
# cases are covered), it definitely is an error worth reporting
self . error ( " function has an explicit return type, but no return statement was found " , node )
hasVal = hasVal and not typ . isNil ( )
for jump in self . currentFunction . valueType . retJumps :
self . patchJump ( jump )
self . endScope ( )
# Terminates the function's context
2023-05-01 16:27:00 +02:00
let stop = self . chunk . code . len ( ) . toTriple ( )
2022-12-06 12:55:05 +01:00
self . emitByte ( OpCode . Return , self . peek ( ) . token . line )
if hasVal :
self . emitByte ( 1 , self . peek ( ) . token . line )
else :
self . emitByte ( 0 , self . peek ( ) . token . line )
self . chunk . functions [ idx ] = stop [ 0 ]
self . chunk . functions [ idx + 1 ] = stop [ 1 ]
self . chunk . functions [ idx + 2 ] = stop [ 2 ]
offset . stop = self . chunk . code . len ( )
# Well, we've compiled everything: time to patch
# the jump offset
self . patchJump ( jmp )
2022-08-30 12:55:14 +02:00
# Restores the enclosing function (if any).
# Makes nested calls work (including recursion)
self . currentFunction = function
2022-12-04 16:54:18 +01:00
self . stackIndex = stackIdx
2022-06-02 01:33:56 +02:00
2022-05-22 17:23:52 +02:00
2023-01-22 17:58:32 +01:00
proc typeDecl ( self : BytecodeCompiler , node : TypeDecl , name : Name ) =
## Compiles type declarations
for field in node . fields :
if self . compare ( self . inferOrError ( field . valueType ) , name . valueType ) and not node . isRef :
self . error ( & " illegal type recursion for non-ref type ' {name.ident.token.lexeme} ' " )
2022-12-15 11:48:36 +01:00
proc declaration ( self : BytecodeCompiler , node : Declaration ) =
2022-12-04 16:54:18 +01:00
## Compiles declarations, statements and expressions
## recursively
2022-04-04 12:29:23 +02:00
case node . kind :
2022-11-28 18:21:38 +01:00
of NodeKind . funDecl :
2022-11-29 16:48:05 +01:00
var name = self . declare ( node )
2022-12-05 12:06:24 +01:00
if not name . valueType . isAuto :
2023-01-05 12:44:11 +01:00
# We can't compile automatic functions right
# away because we need to know the type of the
# arguments in their signature, and this info is
# not available at declaration time
2022-12-05 17:09:09 +01:00
self . funDecl ( FunDecl ( node ) , name )
2022-12-05 12:06:24 +01:00
if name . isGeneric :
# After we're done compiling a generic
2023-01-05 12:44:11 +01:00
# function, we pull a magic trick: since
# from here on the user will be able to
2022-12-05 12:06:24 +01:00
# call this with any of the types in the
# generic constraint, we switch every generic
# to a type union (which, conveniently, have an
# identical layout) so that the compiler will
# typecheck the function as if its arguments
# were all types of the constraint at once,
# while still allowing the user to call it with
# any type in said constraint
for i , argument in name . valueType . args :
if argument . kind . kind ! = Generic :
continue
else :
argument . kind . asUnion = true
if not name . valueType . returnType . isNil ( ) and name . valueType . returnType . kind = = Generic :
name . valueType . returnType . asUnion = true
2022-11-28 18:21:38 +01:00
of NodeKind . typeDecl :
2023-01-22 17:58:32 +01:00
self . typeDecl ( TypeDecl ( node ) , self . declare ( node ) )
2022-11-05 14:27:57 +01:00
of NodeKind . varDecl :
2023-01-22 17:58:32 +01:00
self . varDecl ( VarDecl ( node ) )
2022-04-04 12:29:23 +02:00
else :
2022-12-05 17:09:09 +01:00
self . statement ( Statement ( node ) )
2022-04-04 12:29:23 +02:00
2022-12-15 11:48:36 +01:00
proc compile * ( self : BytecodeCompiler , ast : seq [ Declaration ] , file : string , lines : seq [ tuple [ start , stop : int ] ] , source : string , chunk : Chunk = nil ,
2022-11-23 10:40:45 +01:00
incremental : bool = false , isMainModule : bool = true , disabledWarnings : seq [ WarningKind ] = @ [ ] , showMismatches : bool = false ,
mode : CompileMode = Debug ) : Chunk =
2022-08-15 11:46:24 +02:00
## Compiles a sequence of AST nodes into a chunk
## object
if chunk . isNil ( ) :
self . chunk = newChunk ( )
else :
self . chunk = chunk
self . file = file
2022-11-02 13:16:43 +01:00
self . depth = 0
2022-08-15 11:46:24 +02:00
self . currentFunction = nil
2023-03-27 17:57:18 +02:00
if self . replMode :
self . ast & = ast
self . source & = " \n " & source
self . lines & = lines
else :
self . ast = ast
self . current = 0
self . lines = lines
self . source = source
2022-10-17 11:28:00 +02:00
self . isMainModule = isMainModule
2022-11-22 15:13:42 +01:00
self . disabledWarnings = disabledWarnings
2022-11-23 01:02:35 +01:00
self . showMismatches = showMismatches
2022-11-23 10:40:45 +01:00
self . mode = mode
2023-05-22 12:57:38 +02:00
let start = self . chunk . code . len ( )
2022-10-17 11:28:00 +02:00
if not incremental :
self . jumps = @ [ ]
2023-05-22 14:38:03 +02:00
self . modules = newTable [ string , Name ] ( )
2023-06-01 12:56:59 +02:00
self . stackIndex = 1
2022-10-17 11:28:00 +02:00
let pos = self . beginProgram ( )
2022-04-04 12:29:23 +02:00
while not self . done ( ) :
2022-05-04 14:27:15 +02:00
self . declaration ( Declaration ( self . step ( ) ) )
2022-10-17 11:28:00 +02:00
self . terminateProgram ( pos )
2022-04-04 12:29:23 +02:00
result = self . chunk
2022-08-15 11:46:24 +02:00
2023-01-17 12:53:23 +01:00
proc compileModule ( self : BytecodeCompiler , module : Name ) =
2022-10-17 11:28:00 +02:00
## Compiles an imported module into an existing chunk
## using the compiler's internal parser and lexer objects
var path = " "
2023-05-23 11:13:10 +02:00
let moduleName = module . path & " .pn "
# We take the absolute path of the module so that we
# know that if it's in self.modules, then we already
# imported it
2022-11-03 11:01:28 +01:00
for i , searchPath in moduleLookupPaths :
2022-11-05 10:57:28 +01:00
if searchPath = = " " :
2022-11-29 16:48:05 +01:00
path = absolutePath ( joinPath ( splitPath ( self . file ) . head , moduleName ) )
2022-11-05 10:57:28 +01:00
else :
2023-05-23 11:13:10 +02:00
path = absolutePath ( joinPath ( searchPath , moduleName ) )
2022-10-17 11:28:00 +02:00
if fileExists ( path ) :
break
elif i = = searchPath . high ( ) :
self . error ( & """ could not import ' {path} ' : module not found """ )
2023-05-23 11:13:10 +02:00
module . absPath = path
if self . modules . hasKey ( path ) :
# Module is already imported: we have
# already compiled it
2022-08-16 13:11:09 +02:00
return
2022-08-30 12:55:14 +02:00
let source = readFile ( path )
2023-05-23 11:13:10 +02:00
# Preserve the current state so we can
# resume compiling the current module
# later
2022-10-17 11:28:00 +02:00
let current = self . current
let ast = self . ast
let file = self . file
let lines = self . lines
let src = self . source
2022-11-27 13:39:41 +01:00
let currentModule = self . currentModule
let mainModule = self . isMainModule
let parentModule = self . parentModule
2023-03-27 17:57:18 +02:00
let replMode = self . replMode
self . replMode = false
2023-05-23 11:13:10 +02:00
# Set the current module to the new module
# and the current module as the parent module:
# this is needed for export statements
2022-11-27 13:39:41 +01:00
self . parentModule = currentModule
self . currentModule = module
2023-05-23 11:13:10 +02:00
# We remember where the new module starts, but
# we don't emit the bytes into the chunk right
# away because we may call this function again
# from within this call and it would break all
# sorts of things
2023-05-22 12:57:38 +02:00
let start = self . chunk . code . len ( )
2022-10-17 11:28:00 +02:00
discard self . compile ( self . parser . parse ( self . lexer . lex ( source , path ) ,
path , self . lexer . getLines ( ) ,
2022-11-27 13:39:41 +01:00
self . lexer . getSource ( ) , persist = true ) ,
path , self . lexer . getLines ( ) , self . lexer . getSource ( ) , chunk = self . chunk , incremental = true ,
2022-11-23 10:40:45 +01:00
isMainModule = false , self . disabledWarnings , self . showMismatches , self . mode )
2023-05-23 11:13:10 +02:00
# Mark the boundaries of the module
2023-05-22 12:57:38 +02:00
self . chunk . modules . extend ( start . toTriple ( ) )
self . chunk . modules . extend ( self . chunk . code . high ( ) . toTriple ( ) )
# I swear to god if someone ever creates a peon module with a name that's
# longer than 2^16 bytes I will hit them with a metal pipe. Mark my words
self . chunk . modules . extend ( self . currentModule . ident . token . lexeme . len ( ) . toDouble ( ) )
self . chunk . modules . extend ( self . currentModule . ident . token . lexeme . toBytes ( ) )
2022-11-27 13:39:41 +01:00
module . file = path
# No need to save the old scope depth: import statements are
# only allowed at the top level!
2022-11-02 13:16:43 +01:00
self . depth = 0
2022-10-17 11:28:00 +02:00
self . current = current
self . ast = ast
self . file = file
2022-11-27 13:39:41 +01:00
self . currentModule = currentModule
self . isMainModule = mainModule
self . parentModule = parentModule
2023-03-27 17:57:18 +02:00
self . replMode = replMode
2022-10-17 11:28:00 +02:00
self . lines = lines
self . source = src
2023-05-23 11:13:10 +02:00
self . modules [ module . absPath ] = module