Further work on porting the bytecode target
This commit is contained in:
parent
6181c49f1f
commit
83051d67f8
|
@ -1,10 +1,46 @@
|
|||
import frontend/compiler/typechecker
|
||||
import backend/bytecode/opcodes
|
||||
import backend/bytecode/tooling/multibyte
|
||||
import errors
|
||||
|
||||
|
||||
import std/strutils
|
||||
import std/parseutils
|
||||
import std/tables
|
||||
import std/strformat
|
||||
|
||||
|
||||
type
|
||||
|
||||
FunctionWrapper = ref object
|
||||
## A wrapper around a typed function
|
||||
## declaration. This is necessary to
|
||||
## carry bytecode-specific information
|
||||
## regarding this function along with
|
||||
## the typed declaration itself
|
||||
decl: TypedFunDecl
|
||||
location: int
|
||||
|
||||
BytecodeGenerator* = ref object
|
||||
## A bytecode generator
|
||||
|
||||
# The piece of code we compile into
|
||||
chunk: Chunk
|
||||
# The current size of the call
|
||||
# stack (which is always known
|
||||
# statically)
|
||||
stackSize: int
|
||||
# Stores the position of all jumps
|
||||
jumps: seq[tuple[patched: bool, offset: int]]
|
||||
# Metadata regarding function locations (used to construct
|
||||
# the debugging fields in the resulting bytecode)
|
||||
functions: seq[tuple[start, stop, pos: int, fn: Name]]
|
||||
# Used for error reporting
|
||||
currentFile: string
|
||||
currentNode: TypedNode
|
||||
# The typechecker used to validate the peon code we're generating
|
||||
# bytecode for
|
||||
typeChecker: TypeChecker
|
||||
|
||||
|
||||
proc newBytecodeGenerator*: BytecodeGenerator =
|
||||
|
@ -13,18 +49,384 @@ proc newBytecodeGenerator*: BytecodeGenerator =
|
|||
result = BytecodeGenerator()
|
||||
|
||||
|
||||
proc generateExpression(self: BytecodeGenerator, node: TypedExpr) =
|
||||
## Emits code for expressions
|
||||
|
||||
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr)
|
||||
|
||||
proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode]): Chunk =
|
||||
|
||||
proc error(self: BytecodeGenerator, msg: string, typedNode: TypedNode = nil) {.raises: [CodeGenError].} =
|
||||
## Raises a generic peon exception
|
||||
var typedNode = typedNode
|
||||
var file = self.currentFile
|
||||
if typedNode.isNil():
|
||||
typedNode = self.currentNode
|
||||
if file == "" and typedNode.node.isDecl():
|
||||
file = TypedDecl(typedNode).name.owner.ident.token.lexeme
|
||||
raise CodeGenError(msg: msg, line: typedNode.node.token.line, file: file)
|
||||
|
||||
|
||||
proc emitByte(self: BytecodeGenerator, byt: OpCode | uint8, line: int) {.inline.} =
|
||||
## Emits a single byte, writing it to
|
||||
## the current chunk being compiled
|
||||
self.chunk.write(uint8(byt), line)
|
||||
|
||||
|
||||
proc emitBytes(self: BytecodeGenerator, bytarr: openarray[OpCode | uint8], line: int) {.inline.} =
|
||||
## Handy helper method to write arbitrary bytes into
|
||||
## the current chunk, calling emitByte on each of its
|
||||
## elements
|
||||
for b in bytarr:
|
||||
self.emitByte(b, line)
|
||||
|
||||
|
||||
proc makeConstant(self: BytecodeGenerator, value: TypedExpr): array[3, uint8] =
|
||||
## Adds a constant to the current chunk's constant table
|
||||
## and returns its index as a 3-byte array of uint8s
|
||||
var lit: string
|
||||
if value.kind.kind == Integer:
|
||||
lit = value.node.token.lexeme
|
||||
if lit.contains("'"):
|
||||
var idx = lit.high()
|
||||
while lit[idx] != '\'':
|
||||
lit = lit[0..^2]
|
||||
dec(idx)
|
||||
lit = lit[0..^2]
|
||||
case value.kind.kind:
|
||||
of Integer:
|
||||
case value.kind.size:
|
||||
of Tiny:
|
||||
result = self.chunk.writeConstant([uint8(parseInt(lit))])
|
||||
of Short:
|
||||
result = self.chunk.writeConstant(parseInt(lit).toDouble())
|
||||
of Long:
|
||||
result = self.chunk.writeConstant(parseInt(lit).toQuad())
|
||||
of LongLong:
|
||||
if not value.kind.signed:
|
||||
result = self.chunk.writeConstant(parseInt(lit).toLong())
|
||||
else:
|
||||
result = self.chunk.writeConstant(parseBiggestUInt(lit).toLong())
|
||||
of String:
|
||||
result = self.chunk.writeConstant(value.node.token.lexeme[1..^1].toBytes())
|
||||
of Float:
|
||||
case value.kind.width:
|
||||
of Half:
|
||||
var f: float = 0.0
|
||||
discard parseFloat(value.node.token.lexeme, f)
|
||||
result = self.chunk.writeConstant(cast[array[4, uint8]](float32(f)))
|
||||
of Full:
|
||||
var f: float = 0.0
|
||||
discard parseFloat(value.node.token.lexeme, f)
|
||||
result = self.chunk.writeConstant(cast[array[8, uint8]](f))
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc emitConstant(self: BytecodeGenerator, expression: TypedExpr) =
|
||||
## Emits a constant instruction along
|
||||
## with its operand
|
||||
let
|
||||
typ = expression.kind
|
||||
node = expression.node
|
||||
case typ.kind:
|
||||
of Integer:
|
||||
case typ.size:
|
||||
of LongLong:
|
||||
if typ.signed:
|
||||
self.emitByte(LoadInt64, node.token.line)
|
||||
else:
|
||||
self.emitByte(LoadUInt64, node.token.line)
|
||||
of Long:
|
||||
if typ.signed:
|
||||
self.emitByte(LoadInt32, node.token.line)
|
||||
else:
|
||||
self.emitByte(LoadUInt32, node.token.line)
|
||||
of Short:
|
||||
if typ.signed:
|
||||
self.emitByte(LoadInt16, node.token.line)
|
||||
else:
|
||||
self.emitByte(LoadUInt16, node.token.line)
|
||||
of Tiny:
|
||||
if typ.signed:
|
||||
self.emitByte(LoadInt8, node.token.line)
|
||||
else:
|
||||
self.emitByte(LoadUInt8, node.token.line)
|
||||
of String:
|
||||
self.emitByte(LoadString, node.token.line)
|
||||
let str = LiteralExpr(node).literal.lexeme
|
||||
if str.len() >= 16777216:
|
||||
self.error("string constants cannot be larger than 16777215 bytes", expression)
|
||||
self.emitBytes((str.len() - 2).toTriple(), node.token.line)
|
||||
of Float:
|
||||
case typ.width:
|
||||
of Half:
|
||||
self.emitByte(LoadFloat32, node.token.line)
|
||||
of Full:
|
||||
self.emitByte(LoadFloat64, node.token.line)
|
||||
else:
|
||||
discard # TODO
|
||||
self.emitBytes(self.makeConstant(expression), node.token.line)
|
||||
|
||||
|
||||
proc setJump(self: BytecodeGenerator, offset: int, jmp: array[3, uint8]) =
|
||||
## 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]
|
||||
|
||||
|
||||
proc setJump(self: BytecodeGenerator, offset: int, jmp: seq[uint8]) =
|
||||
## 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]
|
||||
|
||||
|
||||
proc emitJump(self: BytecodeGenerator, opcode: OpCode, line: int): int =
|
||||
## 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()
|
||||
|
||||
|
||||
proc patchJump(self: BytecodeGenerator, 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
|
||||
|
||||
|
||||
proc handleBuiltinFunction(self: BytecodeGenerator, fn: FunctionWrapper, args: seq[TypedExpr], line: int) =
|
||||
## Emits instructions for builtin functions
|
||||
## such as addition or subtraction
|
||||
if fn.decl.name.valueType.builtinOp notin ["LogicalOr", "LogicalAnd"]:
|
||||
if len(args) == 2:
|
||||
self.generateExpression(args[1])
|
||||
self.generateExpression(args[0])
|
||||
elif len(args) == 1:
|
||||
self.generateExpression(args[0])
|
||||
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,
|
||||
"SignedLessThan": SignedLessThan,
|
||||
"SignedGreaterThan": SignedGreaterThan,
|
||||
"SignedLessOrEqual": SignedLessOrEqual,
|
||||
"SignedGreaterOrEqual": SignedGreaterOrEqual,
|
||||
"Float32LessThan": Float32LessThan,
|
||||
"Float32GreaterThan": Float32GreaterThan,
|
||||
"Float32LessOrEqual": Float32LessOrEqual,
|
||||
"Float32GreaterOrEqual": Float32GreaterOrEqual,
|
||||
"Float64LessThan": Float64LessThan,
|
||||
"Float64GreaterThan": Float64GreaterThan,
|
||||
"Float64LessOrEqual": Float64LessOrEqual,
|
||||
"Float64GreaterOrEqual": Float64GreaterOrEqual,
|
||||
"PrintString": PrintString,
|
||||
"SysClock64": SysClock64,
|
||||
"LogicalNot": LogicalNot,
|
||||
"NegInf": LoadNInf,
|
||||
"Identity": Identity
|
||||
}.toTable()
|
||||
if fn.decl.name.valueType.builtinOp == "print":
|
||||
let typ = args[0].kind
|
||||
case typ.kind:
|
||||
of Integer:
|
||||
case typ.size:
|
||||
of LongLong:
|
||||
if typ.signed:
|
||||
self.emitByte(PrintInt64, line)
|
||||
else:
|
||||
self.emitByte(PrintUInt64, line)
|
||||
of Long:
|
||||
if typ.signed:
|
||||
self.emitByte(PrintInt32, line)
|
||||
else:
|
||||
self.emitByte(PrintUInt32, line)
|
||||
of Short:
|
||||
if typ.signed:
|
||||
self.emitByte(PrintInt16, line)
|
||||
else:
|
||||
self.emitByte(PrintUInt16, line)
|
||||
of Tiny:
|
||||
if typ.signed:
|
||||
self.emitByte(PrintInt8, line)
|
||||
else:
|
||||
self.emitByte(PrintUInt8, line)
|
||||
of Float:
|
||||
case typ.width:
|
||||
of Full:
|
||||
self.emitByte(PrintFloat64, line)
|
||||
of Half:
|
||||
self.emitByte(PrintFloat32, line)
|
||||
of String:
|
||||
self.emitByte(PrintString, line)
|
||||
of Boolean:
|
||||
self.emitByte(PrintBool, line)
|
||||
of TypeKind.Nan:
|
||||
self.emitByte(PrintNan, line)
|
||||
of TypeKind.Infinity:
|
||||
self.emitByte(PrintInf, line)
|
||||
of Function:
|
||||
self.emitByte(LoadString, line)
|
||||
var loc: string = fn.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)
|
||||
else:
|
||||
self.error(&"invalid type {self.typechecker.stringify(typ)} for built-in 'print'", args[0])
|
||||
return
|
||||
if fn.decl.name.valueType.builtinOp in codes:
|
||||
self.emitByte(codes[fn.decl.name.valueType.builtinOp], line)
|
||||
return
|
||||
# Some builtin operations are slightly more complex
|
||||
# so we handle them separately
|
||||
case fn.decl.name.valueType.builtinOp:
|
||||
of "LogicalOr":
|
||||
self.generateExpression(args[0])
|
||||
let jump = self.emitJump(JumpIfTrue, line)
|
||||
self.generateExpression(args[1])
|
||||
self.patchJump(jump)
|
||||
of "LogicalAnd":
|
||||
self.generateExpression(args[0])
|
||||
let jump = self.emitJump(JumpIfFalseOrPop, line)
|
||||
self.generateExpression(args[1])
|
||||
self.patchJump(jump)
|
||||
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
|
||||
else:
|
||||
self.error(&"unknown built-in: '{fn.decl.name.valueType.builtinOp}'")
|
||||
|
||||
|
||||
proc patchReturnAddress(self: BytecodeGenerator, pos: int) =
|
||||
## Patches the return address of a function
|
||||
## call
|
||||
let address = self.chunk.code.len().toLong()
|
||||
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]
|
||||
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]
|
||||
|
||||
|
||||
proc generateExpression(self: BytecodeGenerator, expression: TypedExpr) =
|
||||
## Emits code for expressions
|
||||
let
|
||||
typ = expression.kind
|
||||
node = expression.node
|
||||
case typ.kind:
|
||||
of Integer, Float:
|
||||
# No need to do any input validation here: the typechecker
|
||||
# has graciously done all the work for us! :)
|
||||
self.emitConstant(expression)
|
||||
of Infinity:
|
||||
if typ.positive:
|
||||
self.emitByte(LoadInf, node.token.line)
|
||||
else:
|
||||
self.emitByte(LoadNInf, node.token.line)
|
||||
of NaN:
|
||||
self.emitByte(LoadNaN, node.token.line)
|
||||
else:
|
||||
discard # TODO
|
||||
|
||||
|
||||
proc beginProgram(self: BytecodeGenerator): int =
|
||||
## Emits boilerplate code to set up
|
||||
## a peon program
|
||||
self.emitByte(LoadUInt64, 1)
|
||||
# The initial jump address is always the same
|
||||
self.emitBytes(self.chunk.writeConstant(12.toLong()), 1)
|
||||
self.emitByte(LoadUInt64, 1)
|
||||
# We emit a dummy return address which is patched later
|
||||
self.emitBytes(self.chunk.writeConstant(0.toLong()), 1)
|
||||
result = self.chunk.consts.len() - 8
|
||||
self.emitByte(Call, 1)
|
||||
self.emitBytes(0.toTriple(), 1)
|
||||
|
||||
|
||||
proc endProgram(self: BytecodeGenerator, pos: int) =
|
||||
## Emits boilerplate code to tear down
|
||||
## a peon program
|
||||
self.emitByte(OpCode.Return, self.currentNode.node.token.line)
|
||||
# Entry point has no return value
|
||||
self.emitByte(0, self.currentNode.node.token.line)
|
||||
# Patch the return address now that we know the boundaries
|
||||
# of the function
|
||||
self.patchReturnAddress(pos)
|
||||
|
||||
|
||||
proc generate*(self: BytecodeGenerator, compiled: seq[TypedNode], typeChecker: TypeChecker): Chunk =
|
||||
## Turn the given compilation output
|
||||
## into a bytecode chunk
|
||||
self.chunk = newChunk()
|
||||
self.typeChecker = typeChecker
|
||||
let offset = self.beginProgram()
|
||||
for typedNode in compiled:
|
||||
self.currentNode = typedNode
|
||||
let currentFile = self.currentFile
|
||||
if self.currentNode.node.isDecl():
|
||||
self.currentFile = TypedDecl(typedNode).name.ident.token.lexeme
|
||||
case typedNode.node.kind:
|
||||
of exprStmt:
|
||||
self.generateExpression(TypedExpr(typedNode))
|
||||
self.generateExpression(TypedExprStmt(typedNode).expression)
|
||||
self.emitByte(Pop, typedNode.node.token.line)
|
||||
else:
|
||||
discard # TODO
|
||||
self.currentFile = currentFile
|
||||
self.endProgram(offset)
|
||||
result = self.chunk
|
||||
|
|
|
@ -39,6 +39,7 @@ type
|
|||
commit*: string
|
||||
compileDate*: int
|
||||
chunk*: Chunk
|
||||
size*: int
|
||||
SerializationError* = ref object of PeonException
|
||||
|
||||
|
||||
|
@ -221,6 +222,7 @@ proc loadBytes*(self: BytecodeSerializer, stream: seq[byte]): SerializedBytecode
|
|||
discard self.newBytecodeSerializer()
|
||||
new(result)
|
||||
result.chunk = newChunk()
|
||||
result.size = stream.len()
|
||||
self.chunk = result.chunk
|
||||
var stream = stream
|
||||
try:
|
||||
|
|
|
@ -333,22 +333,11 @@ proc collect(self: var PeonVM) =
|
|||
|
||||
# Implementation of the peon VM
|
||||
|
||||
proc initCache*(self: var PeonVM) =
|
||||
## Initializes the VM's
|
||||
## singletons cache
|
||||
self.cache[0] = 0x0 # False
|
||||
self.cache[1] = 0x1 # True
|
||||
self.cache[2] = 0x2 # Nil
|
||||
self.cache[3] = 0x3 # Positive inf
|
||||
self.cache[4] = 0x4 # Negative inf
|
||||
self.cache[5] = 0x5 # NaN
|
||||
|
||||
|
||||
proc newPeonVM*: PeonVM =
|
||||
## Initializes a new, blank VM
|
||||
## for executing Peon bytecode
|
||||
result.ip = 0
|
||||
result.initCache()
|
||||
result.gc = newPeonGC()
|
||||
result.frames = @[]
|
||||
result.operands = @[]
|
||||
|
@ -363,15 +352,16 @@ func getNil*(self: var PeonVM): uint64 = self.cache[2]
|
|||
|
||||
func getBool*(self: var PeonVM, value: bool): uint64 =
|
||||
if value:
|
||||
return self.cache[1]
|
||||
return self.cache[0]
|
||||
return 0
|
||||
return 1
|
||||
|
||||
func getInf*(self: var PeonVM, positive: bool): uint64 =
|
||||
if positive:
|
||||
return self.cache[3]
|
||||
return self.cache[4]
|
||||
return cast[uint64](Inf)
|
||||
return cast[uint64](-Inf)
|
||||
|
||||
func getNan*(self: var PeonVM): uint64 = self.cache[5]
|
||||
|
||||
func getNan*(self: var PeonVM): uint64 = cast[uint64](NaN)
|
||||
|
||||
|
||||
# Thanks to nim's *genius* idea of making x > y a template
|
||||
|
@ -1042,11 +1032,11 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
|
|||
try:
|
||||
self.dispatch()
|
||||
except Defect as e:
|
||||
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
|
||||
stderr.writeLine(&"VM: Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
|
||||
except CatchableError as e:
|
||||
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
|
||||
stderr.writeLine(&"VM: Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
|
||||
except NilAccessDefect:
|
||||
stderr.writeLine(&"Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
|
||||
stderr.writeLine(&"VM: Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
|
||||
quit(1)
|
||||
if not repl:
|
||||
# We clean up after ourselves!
|
||||
|
|
|
@ -83,7 +83,8 @@ Options
|
|||
-s, --string Run the given string as if it were a file (the filename is set to '<string>')
|
||||
|
||||
The following options are specific to the 'bytecode' backend:
|
||||
-n, --noDump Do not dump bytecode files to the source directory
|
||||
-n, --noDump Do not dump bytecode files to the source directory. Note that
|
||||
no files are dumped when using -s/--string
|
||||
--breakpoints Set debugging breakpoints at the given bytecode offsets.
|
||||
Input should be a comma-separated list of positive integers
|
||||
(spacing is irrelevant). Only works if peon was compiled with
|
||||
|
|
|
@ -19,3 +19,6 @@ type
|
|||
## peon failure (not to be used directly)
|
||||
file*: string # The file where the error occurred
|
||||
line*: int # The line where the error occurred
|
||||
|
||||
CodeGenError* = ref object of PeonException
|
||||
## An exception for a code generation failure
|
|
@ -313,11 +313,11 @@ proc toIntrinsic(name: string): Type =
|
|||
return Type(kind: Byte, intrinsic: true)
|
||||
elif name in ["char", "c"]:
|
||||
return Type(kind: Char, intrinsic: true)
|
||||
elif name == "nan":
|
||||
elif name == "NaN":
|
||||
return Type(kind: TypeKind.Nan, intrinsic: true)
|
||||
elif name == "nil":
|
||||
return Type(kind: Nil, intrinsic: true)
|
||||
elif name == "inf":
|
||||
elif name == "Inf":
|
||||
return Type(kind: Infinity, intrinsic: true, positive: true)
|
||||
elif name == "NegInf":
|
||||
return Type(kind: Infinity, intrinsic: true)
|
||||
elif name == "bool":
|
||||
return Type(kind: Boolean, intrinsic: true)
|
||||
|
@ -342,6 +342,10 @@ proc infer(self: TypeChecker, node: LiteralExpr): TypedExpr =
|
|||
return newTypedExpr(node, "bool".toIntrinsic())
|
||||
of strExpr:
|
||||
return newTypedExpr(node, "string".toIntrinsic())
|
||||
of nanExpr:
|
||||
return newTypedExpr(node, "NaN".toIntrinsic())
|
||||
of infExpr:
|
||||
return newTypedExpr(node, "Inf".toIntrinsic())
|
||||
of intExpr, binExpr, octExpr, hexExpr:
|
||||
let size = node.token.lexeme.split("'")
|
||||
if size.len() == 1:
|
||||
|
@ -442,9 +446,10 @@ proc compare(self: TypeChecker, a, b: Type): bool =
|
|||
# b has a parent and a doesn't
|
||||
if a.parent.isNil() and not b.parent.isNil():
|
||||
return false
|
||||
if not self.compare(a.parent, b.parent):
|
||||
# Different parent
|
||||
return false
|
||||
if not a.parent.isNil() and not b.parent.isNil():
|
||||
if not self.compare(a.parent, b.parent):
|
||||
# Different parent
|
||||
return false
|
||||
# Then the field names
|
||||
var fields: HashSet[string] = initHashSet[string]()
|
||||
for fieldName in a.fields.keys():
|
||||
|
@ -463,7 +468,7 @@ proc compare(self: TypeChecker, a, b: Type): bool =
|
|||
# far as the type system is concerned
|
||||
return true
|
||||
of Boolean, Infinity, Any,
|
||||
Auto, Char, Byte, String, Nil:
|
||||
Auto, Char, Byte, String:
|
||||
return true
|
||||
of Integer:
|
||||
return a.size == b.size and a.signed == b.signed
|
||||
|
@ -517,75 +522,82 @@ proc compare(self: TypeChecker, a, b: Type): bool =
|
|||
return false
|
||||
|
||||
|
||||
proc compare*(self: TypeChecker, a, b: Name): bool =
|
||||
## Compares two names. In addition to checking
|
||||
## that their types match, this function makes
|
||||
## sure the two given objects actually come from
|
||||
## the same module (and so are literally the same
|
||||
## exact object)
|
||||
if not self.compare(a.valueType, b.valueType):
|
||||
return false
|
||||
return a.owner == b.owner
|
||||
|
||||
|
||||
proc literal(self: TypeChecker, node: LiteralExpr): TypedExpr =
|
||||
case node.kind:
|
||||
of trueExpr, falseExpr:
|
||||
result = self.infer(node)
|
||||
of strExpr:
|
||||
of trueExpr, falseExpr, strExpr, infExpr, nanExpr:
|
||||
result = self.infer(node)
|
||||
of intExpr:
|
||||
let y = IntExpr(node)
|
||||
result = self.infer(y)
|
||||
result = self.infer(node)
|
||||
if result.kind.kind == Integer:
|
||||
var x: int
|
||||
try:
|
||||
discard parseInt(y.literal.lexeme, x)
|
||||
discard parseInt(node.literal.lexeme, x)
|
||||
except ValueError:
|
||||
self.error("integer value out of range")
|
||||
else:
|
||||
var x: uint64
|
||||
try:
|
||||
discard parseBiggestUInt(y.literal.lexeme, x)
|
||||
discard parseBiggestUInt(node.literal.lexeme, x)
|
||||
except ValueError:
|
||||
self.error("integer value out of range")
|
||||
of hexExpr:
|
||||
var x: int
|
||||
var y = HexExpr(node)
|
||||
result = self.infer(y)
|
||||
result = self.infer(node)
|
||||
try:
|
||||
discard parseHex(y.literal.lexeme, x)
|
||||
discard parseHex(node.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,
|
||||
stop: y.token.pos.start + len($x)),
|
||||
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
|
||||
pos: (start: node.token.pos.start,
|
||||
stop: node.token.pos.start + len($x)),
|
||||
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
|
||||
)
|
||||
)
|
||||
result.node = node
|
||||
of binExpr:
|
||||
var x: int
|
||||
var y = BinExpr(node)
|
||||
result = self.infer(y)
|
||||
result = self.infer(node)
|
||||
try:
|
||||
discard parseBin(y.literal.lexeme, x)
|
||||
discard parseBin(node.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,
|
||||
stop: y.token.pos.start + len($x)),
|
||||
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
|
||||
pos: (start: node.token.pos.start,
|
||||
stop: node.token.pos.start + len($x)),
|
||||
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
|
||||
)
|
||||
)
|
||||
result.node = node
|
||||
of octExpr:
|
||||
var x: int
|
||||
var y = OctExpr(node)
|
||||
result = self.infer(y)
|
||||
result = self.infer(node)
|
||||
try:
|
||||
discard parseOct(y.literal.lexeme, x)
|
||||
discard parseOct(node.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,
|
||||
stop: y.token.pos.start + len($x)),
|
||||
relPos: (start: y.token.relPos.start, stop: y.token.relPos.start + len($x))
|
||||
let node = newIntExpr(Token(lexeme: $x, line: node.token.line,
|
||||
pos: (start: node.token.pos.start,
|
||||
stop: node.token.pos.start + len($x)),
|
||||
relPos: (start: node.token.relPos.start, stop: node.token.relPos.start + len($x))
|
||||
)
|
||||
)
|
||||
result.node = node
|
||||
of floatExpr:
|
||||
var x: float
|
||||
var y = FloatExpr(node)
|
||||
result = self.infer(y)
|
||||
result = self.infer(node)
|
||||
try:
|
||||
discard parseFloat(y.literal.lexeme, x)
|
||||
discard parseFloat(node.literal.lexeme, x)
|
||||
except ValueError:
|
||||
self.error("floating point value out of range")
|
||||
else:
|
||||
|
@ -680,7 +692,7 @@ proc stringify*(self: TypeChecker, typ: Type): string =
|
|||
if typ.isNil():
|
||||
return "void"
|
||||
case typ.kind:
|
||||
of Char, Byte, String, Nil, TypeKind.Nan,
|
||||
of Char, Byte, String, TypeKind.Nan,
|
||||
Auto, Any:
|
||||
result &= ($typ.kind).toLowerAscii()
|
||||
of CustomType:
|
||||
|
@ -1001,6 +1013,10 @@ proc match(self: TypeChecker, name: string, sig: TypeSignature, node: ASTNode =
|
|||
for (a, b) in zip(result.valueType.args, sig):
|
||||
if not a.kind.isAny() and b.kind.isAny():
|
||||
self.error("any is not a valid type in this context", node)
|
||||
of CustomType:
|
||||
for (a, b) in zip(result.valueType.fields.values().toSeq(), sig):
|
||||
if not a.isAny() and b.kind.isAny():
|
||||
self.error("any is not a valid type in this context", node)
|
||||
else:
|
||||
# TODO
|
||||
discard
|
||||
|
@ -1705,10 +1721,12 @@ proc validate(self: TypeChecker, node: ASTNode): TypedNode =
|
|||
case node.kind:
|
||||
of binaryExpr, unaryExpr, NodeKind.genericExpr, identExpr,
|
||||
groupingExpr, callExpr, intExpr, floatExpr, octExpr,
|
||||
binExpr, hexExpr, trueExpr, falseExpr:
|
||||
binExpr, hexExpr, trueExpr, falseExpr, nanExpr, infExpr:
|
||||
result = self.expression(Expression(node))
|
||||
of exprStmt:
|
||||
result = self.expression(ExprStmt(node).expression)
|
||||
let statement = ExprStmt(node)
|
||||
result = self.expression(statement.expression)
|
||||
result = TypedExprStmt(node: statement, expression: TypedExpr(result))
|
||||
of NodeKind.whileStmt:
|
||||
result = self.whileStmt(WhileStmt(node))
|
||||
of NodeKind.blockStmt:
|
||||
|
|
|
@ -35,7 +35,6 @@ type
|
|||
Auto,
|
||||
Byte,
|
||||
Char,
|
||||
Nil,
|
||||
CustomType,
|
||||
EnumEntry,
|
||||
Reference,
|
||||
|
@ -215,6 +214,9 @@ type
|
|||
TypedStmt* = ref object of TypedNode
|
||||
## A typed statement node
|
||||
|
||||
TypedExprStmt* = ref object of TypedStmt
|
||||
expression*: TypedExpr
|
||||
|
||||
TypedBlockStmt* = ref object of TypedStmt
|
||||
## A typed block statement
|
||||
body*: seq[TypedNode]
|
||||
|
|
|
@ -75,6 +75,8 @@ type
|
|||
hexExpr,
|
||||
octExpr,
|
||||
binExpr,
|
||||
nanExpr,
|
||||
infExpr,
|
||||
identExpr, # Identifier
|
||||
pragmaExpr,
|
||||
refExpr,
|
||||
|
@ -124,17 +126,6 @@ type
|
|||
# although arguably less useful (and probably significantly slower than bigints)
|
||||
literal*: Token
|
||||
|
||||
IntExpr* = ref object of LiteralExpr
|
||||
OctExpr* = ref object of LiteralExpr
|
||||
HexExpr* = ref object of LiteralExpr
|
||||
BinExpr* = ref object of LiteralExpr
|
||||
FloatExpr* = ref object of LiteralExpr
|
||||
StrExpr* = ref object of LiteralExpr
|
||||
CharExpr* = ref object of LiteralExpr
|
||||
|
||||
TrueExpr* = ref object of LiteralExpr
|
||||
FalseExpr* = ref object of LiteralExpr
|
||||
|
||||
IdentExpr* = ref object of Expression
|
||||
name*: Token
|
||||
depth*: int
|
||||
|
@ -314,12 +305,18 @@ proc isConst*(self: ASTNode): bool =
|
|||
## constants
|
||||
case self.kind:
|
||||
of intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr,
|
||||
floatExpr:
|
||||
floatExpr, nanExpr, infExpr:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
proc isDecl*(self: ASTNode): bool =
|
||||
## Returns true if the given AST node
|
||||
## represents a declaration
|
||||
return self.kind in [typeDecl, funDecl, varDecl]
|
||||
|
||||
|
||||
## AST node constructors
|
||||
proc newASTNode*(kind: NodeKind, token: Token): ASTNode =
|
||||
## Initializes a new generic ASTNode object
|
||||
|
@ -374,50 +371,49 @@ proc newSwitchStmt*(switch: Expression, branches: seq[tuple[cond: Expression, bo
|
|||
result.default = default
|
||||
|
||||
|
||||
proc newIntExpr*(literal: Token): IntExpr =
|
||||
result = IntExpr(kind: intExpr)
|
||||
proc newIntExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: intExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newOctExpr*(literal: Token): OctExpr =
|
||||
result = OctExpr(kind: octExpr)
|
||||
proc newOctExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: octExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newHexExpr*(literal: Token): HexExpr =
|
||||
result = HexExpr(kind: hexExpr)
|
||||
proc newHexExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: hexExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newBinExpr*(literal: Token): BinExpr =
|
||||
result = BinExpr(kind: binExpr)
|
||||
proc newBinExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: binExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newFloatExpr*(literal: Token): FloatExpr =
|
||||
result = FloatExpr(kind: floatExpr)
|
||||
proc newFloatExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: floatExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newTrueExpr*(token: Token): LiteralExpr = LiteralExpr(kind: trueExpr,
|
||||
token: token, literal: token)
|
||||
proc newFalseExpr*(token: Token): LiteralExpr = LiteralExpr(kind: falseExpr,
|
||||
token: token, literal: token)
|
||||
proc newTrueExpr*(token: Token): LiteralExpr = LiteralExpr(kind: trueExpr, token: token, literal: token)
|
||||
proc newFalseExpr*(token: Token): LiteralExpr = LiteralExpr(kind: falseExpr, token: token, literal: token)
|
||||
proc newNanExpr*(token: Token): LiteralExpr = LiteralExpr(kind: nanExpr, token: token, literal: token)
|
||||
proc newInfExpr*(token: Token): LiteralExpr = LiteralExpr(kind: infExpr, token: token, literal: token)
|
||||
|
||||
|
||||
proc newStrExpr*(literal: Token): StrExpr =
|
||||
result = StrExpr(kind: strExpr)
|
||||
proc newStrExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: strExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
||||
proc newCharExpr*(literal: Token): CharExpr =
|
||||
result = CharExpr(kind: charExpr)
|
||||
proc newCharExpr*(literal: Token): LiteralExpr =
|
||||
result = LiteralExpr(kind: charExpr)
|
||||
result.literal = literal
|
||||
result.token = literal
|
||||
|
||||
|
|
|
@ -377,6 +377,10 @@ proc primary(self: Parser): Expression =
|
|||
result = newBinExpr(self.step())
|
||||
of String:
|
||||
result = newStrExpr(self.step())
|
||||
of TokenType.Inf:
|
||||
result = newInfExpr(self.step())
|
||||
of TokenType.Nan:
|
||||
result = newNanExpr(self.step())
|
||||
of Function:
|
||||
discard self.step()
|
||||
result = Expression(self.funDecl(isLambda=true))
|
||||
|
|
|
@ -41,7 +41,7 @@ type
|
|||
|
||||
# Literal types
|
||||
Integer, Float, String, Identifier,
|
||||
Binary, Octal, Hex, Char
|
||||
Binary, Octal, Hex, Char, Nan, Inf
|
||||
|
||||
# Brackets, parentheses,
|
||||
# operators and others
|
||||
|
|
52
src/main.nim
52
src/main.nim
|
@ -111,7 +111,14 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
|
|||
if debugTypeChecker:
|
||||
styledEcho fgCyan, "Typechecker output:"
|
||||
for typedNode in typedNodes:
|
||||
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
|
||||
case typedNode.node.kind:
|
||||
of exprStmt:
|
||||
# *Technically* an expression statement has no type, but that isn't really useful for debug
|
||||
# purposes, so we print the type of the expression within it instead
|
||||
let exprNode = TypedExprStmt(typedNode).expression
|
||||
styledEcho fgGreen, &"\t{typedNode.node} -> {exprNode.node} -> {typeChecker.stringify(TypedExprStmt(typedNode).expression)}\n"
|
||||
else:
|
||||
styledEcho fgGreen, &"\t{typedNode.node} -> {typeChecker.stringify(typedNode)}\n"
|
||||
case backend:
|
||||
of PeonBackend.Bytecode:
|
||||
var
|
||||
|
@ -122,22 +129,24 @@ proc runFile(filename: string, fromString: bool = false, dump: bool = true, brea
|
|||
chunk: Chunk = newChunk()
|
||||
serialized: SerializedBytecode
|
||||
if not isBinary:
|
||||
chunk = generator.generate(typedNodes)
|
||||
chunk = generator.generate(typedNodes, typeChecker)
|
||||
serialized = serializer.loadBytes(serializer.dumpBytes(chunk, filename))
|
||||
else:
|
||||
serialized = serializer.loadFile(filename)
|
||||
chunk = serialized.chunk
|
||||
if dump:
|
||||
if dump and not fromString:
|
||||
if not output.endsWith(".pbc"):
|
||||
output.add(".pbc")
|
||||
serializer.dumpFile(chunk, filename, output)
|
||||
if debugCompiler:
|
||||
styledEcho fgCyan, "Disassembler output: "
|
||||
styledEcho fgCyan, "Disassembler output below"
|
||||
debugger.disassembleChunk(chunk, filename)
|
||||
if debugSerializer:
|
||||
styledEcho fgCyan, "Serializer output: "
|
||||
styledEcho fgCyan, "Serializer checks: "
|
||||
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
|
||||
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
|
||||
stdout.styledWriteLine(fgBlue, "\t- Total binary size: ", fgYellow, formatSize(serialized.size))
|
||||
|
||||
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
|
||||
if serialized.chunk.consts == chunk.consts:
|
||||
styledEcho fgGreen, "OK"
|
||||
|
@ -245,39 +254,6 @@ proc repl(warnings: seq[WarningKind] = @[], showMismatches: bool = false) =
|
|||
except TypeCheckError:
|
||||
print(TypeCheckError(getCurrentException()))
|
||||
quit(0)
|
||||
#[
|
||||
var
|
||||
lexer = newLexer()
|
||||
parser = newParser()
|
||||
compiler = newPeonCompiler()
|
||||
source: string
|
||||
file = "test.pn"
|
||||
warnings: seq[WarningKind] = @[]
|
||||
for i in WarningKind.low()..WarningKind.high():
|
||||
warnings.add(WarningKind(i))
|
||||
lexer.fillSymbolTable()
|
||||
while true:
|
||||
stdout.write(">>> ")
|
||||
stdout.flushFile()
|
||||
try:
|
||||
source = stdin.readLine()
|
||||
for typedNode in compiler.compile(parser.parse(lexer.lex(source, file), file, lexer.getLines(), lexer.getSource()), lexer.getFile(), lexer.getSource(),
|
||||
showshowMismatches=true, disabledWarnings=warnings):
|
||||
echo &"{typedNode.node} -> {compiler.stringify(typedNode)}\n"
|
||||
except IOError:
|
||||
echo ""
|
||||
break
|
||||
except LexingError as exc:
|
||||
print(exc)
|
||||
except ParseError as exc:
|
||||
print(exc)
|
||||
except CompileError as exc:
|
||||
print(exc)
|
||||
|
||||
when isMainModule:
|
||||
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
||||
main()
|
||||
]#
|
||||
]#
|
||||
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ proc fillSymbolTable*(tokenizer: Lexer) =
|
|||
tokenizer.symbols.addKeyword("false", TokenType.False)
|
||||
tokenizer.symbols.addKeyword("ref", TokenType.Ref)
|
||||
tokenizer.symbols.addKeyword("ptr", TokenType.Ptr)
|
||||
tokenizer.symbols.addKeyword("nan", TokenType.Nan)
|
||||
tokenizer.symbols.addKeyword("inf", TokenType.Inf)
|
||||
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
|
||||
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!", "%", "&", "|", "^",
|
||||
">>", "<<"]:
|
||||
|
|
Loading…
Reference in New Issue