mirror of https://github.com/japl-lang/japl.git
commit
a910445bd4
115
common.nim
115
common.nim
|
@ -13,11 +13,10 @@
|
|||
# limitations under the License.
|
||||
|
||||
|
||||
import tables
|
||||
import strutils
|
||||
import meta/valueobject
|
||||
import meta/tokenobject
|
||||
import types/objecttype
|
||||
import types/japlvalue
|
||||
import types/function
|
||||
import tables
|
||||
|
||||
|
||||
const FRAMES_MAX* = 400 # TODO: Inspect why the VM crashes if this exceeds 400
|
||||
|
@ -89,31 +88,6 @@ proc delete*(self: CallFrame, idx: int) =
|
|||
self.stack.delete(idx)
|
||||
|
||||
|
||||
func stringify*(value: Value): string =
|
||||
case value.kind:
|
||||
of INTEGER:
|
||||
result = $value.toInt()
|
||||
of DOUBLE:
|
||||
result = $value.toFloat()
|
||||
of BOOL:
|
||||
result = $value.toBool()
|
||||
of NIL:
|
||||
result = "nil"
|
||||
of OBJECT:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](value.obj).stringify
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](value.obj).stringify
|
||||
else:
|
||||
result = value.obj.stringify()
|
||||
of ValueType.Nan:
|
||||
result = "nan"
|
||||
of ValueType.Inf:
|
||||
result = "inf"
|
||||
of MINF:
|
||||
result = "-inf"
|
||||
|
||||
|
||||
## TODO: Move this stuff back to their respective module
|
||||
|
||||
|
@ -131,16 +105,16 @@ proc hashFloat(f: float): uint32 =
|
|||
# TODO: Move this into an hash() method for objects
|
||||
proc hash*(value: Value): uint32 =
|
||||
case value.kind:
|
||||
of INTEGER:
|
||||
of ValueType.Integer:
|
||||
result = uint32 value.toInt()
|
||||
of BOOL:
|
||||
of ValueType.Bool:
|
||||
if value.boolValue:
|
||||
result = uint32 1
|
||||
else:
|
||||
result = uint32 0
|
||||
of DOUBLE:
|
||||
of ValueType.Double:
|
||||
result = hashFloat(value.toFloat())
|
||||
of OBJECT:
|
||||
of ValueType.Object:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = hash(cast[ptr String](value.obj))
|
||||
|
@ -150,78 +124,3 @@ proc hash*(value: Value): uint32 =
|
|||
result = uint32 0
|
||||
|
||||
|
||||
# TODO: Move this into a bool() method for objects
|
||||
func isFalsey*(value: Value): bool =
|
||||
case value.kind:
|
||||
of BOOL:
|
||||
result = not value.toBool()
|
||||
of OBJECT:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](value.obj).isFalsey()
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](value.obj).isFalsey()
|
||||
else:
|
||||
result = isFalsey(value.obj)
|
||||
of INTEGER:
|
||||
result = value.toInt() == 0
|
||||
of DOUBLE:
|
||||
result = value.toFloat() == 0.0
|
||||
of NIL:
|
||||
result = true
|
||||
of ValueType.Inf, ValueType.Minf:
|
||||
result = false
|
||||
of ValueType.Nan:
|
||||
result = true
|
||||
|
||||
|
||||
# TODO: Move this to a toString() method for objects
|
||||
func typeName*(value: Value): string =
|
||||
case value.kind:
|
||||
of ValueType.Bool, ValueType.Nil, ValueType.Double,
|
||||
ValueType.Integer, ValueType.Nan, ValueType.Inf:
|
||||
result = ($value.kind).toLowerAscii()
|
||||
of MINF:
|
||||
result = "inf"
|
||||
of OBJECT:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](value.obj).typeName()
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](value.obj).typeName()
|
||||
else:
|
||||
result = value.obj.typeName()
|
||||
|
||||
# TODO: Move this to a eq() method for objects
|
||||
proc valuesEqual*(a: Value, b: Value): bool =
|
||||
if a.kind != b.kind:
|
||||
result = false
|
||||
else:
|
||||
case a.kind:
|
||||
of BOOL:
|
||||
result = a.toBool() == b.toBool()
|
||||
of NIL:
|
||||
result = true
|
||||
of INTEGER:
|
||||
result = a.toInt() == b.toInt()
|
||||
of DOUBLE:
|
||||
result = a.toFloat() == b.toFloat()
|
||||
of OBJECT:
|
||||
case a.obj.kind:
|
||||
of ObjectType.String:
|
||||
var a = cast[ptr String](a.obj)
|
||||
var b = cast[ptr String](b.obj)
|
||||
result = valuesEqual(a, b)
|
||||
of ObjectType.Function:
|
||||
var a = cast[ptr Function](a.obj)
|
||||
var b = cast[ptr Function](b.obj)
|
||||
result = valuesEqual(a, b)
|
||||
else:
|
||||
result = valuesEqual(a.obj, b.obj)
|
||||
of ValueType.Inf:
|
||||
result = b.kind == ValueType.Inf
|
||||
of MINF:
|
||||
result = b.kind == ValueType.Minf
|
||||
of ValueType.Nan:
|
||||
result = false
|
||||
|
||||
|
|
15
compiler.nim
15
compiler.nim
|
@ -20,14 +20,13 @@ import algorithm
|
|||
import strformat
|
||||
import lexer
|
||||
import common
|
||||
import meta/chunk
|
||||
import meta/opcode
|
||||
import meta/tokenobject
|
||||
import meta/valueobject
|
||||
import meta/tokentype
|
||||
import meta/looptype
|
||||
import types/japlvalue
|
||||
import types/stringtype
|
||||
import types/functiontype
|
||||
import types/objecttype
|
||||
import types/function
|
||||
import tables
|
||||
when isMainModule:
|
||||
import util/debug
|
||||
|
@ -177,7 +176,7 @@ proc makeLongConstant(self: ref Compiler, val: Value): array[3, uint8] =
|
|||
proc emitConstant(self: ref Compiler, value: Value) =
|
||||
## Emits a Constant or ConstantLong instruction along
|
||||
## with its operand
|
||||
if self.currentChunk().consts.values.len > 255:
|
||||
if self.currentChunk().consts.len > 255:
|
||||
self.emitByte(OpCode.ConstantLong)
|
||||
self.emitBytes(self.makeLongConstant(value))
|
||||
else:
|
||||
|
@ -581,7 +580,7 @@ proc varDeclaration(self: ref Compiler) =
|
|||
var shortName: uint8
|
||||
var longName: array[3, uint8]
|
||||
var useShort: bool = true
|
||||
if self.currentChunk.consts.values.len < 255:
|
||||
if self.currentChunk.consts.len < 255:
|
||||
shortName = self.parseVariable("Expecting variable name")
|
||||
else:
|
||||
useShort = false
|
||||
|
@ -617,7 +616,7 @@ proc deleteVariable(self: ref Compiler, canAssign: bool) =
|
|||
else:
|
||||
code = OpCode.DeleteLocal
|
||||
self.localCount = self.localCount - 1
|
||||
if self.currentChunk.consts.values.len < 255:
|
||||
if self.currentChunk.consts.len < 255:
|
||||
var name = self.identifierConstant(self.parser.previous())
|
||||
self.emitBytes(code, name)
|
||||
else:
|
||||
|
@ -900,7 +899,7 @@ proc parseFunction(self: ref Compiler, funType: FunctionType) =
|
|||
self.parseBlock()
|
||||
var fun = self.endCompiler()
|
||||
self = self.enclosing
|
||||
if self.currentChunk.consts.values.len < 255:
|
||||
if self.currentChunk.consts.len < 255:
|
||||
self.emitBytes(OpCode.Constant, self.makeConstant(Value(kind: OBJECT, obj: fun)))
|
||||
else:
|
||||
self.emitByte(OpCode.ConstantLong)
|
||||
|
|
|
@ -29,7 +29,8 @@ import strformat
|
|||
import tables
|
||||
import meta/tokentype
|
||||
import meta/tokenobject
|
||||
import meta/valueobject
|
||||
import types/stringtype
|
||||
import types/japlvalue
|
||||
|
||||
# Table of all tokens except reserved keywords
|
||||
const TOKENS = to_table({
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
|
||||
import segfaults
|
||||
import types/objecttype
|
||||
import types/japlvalue
|
||||
|
||||
|
||||
proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer =
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# 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.
|
||||
|
||||
## Base structure for values and objects in JAPL, all
|
||||
## types inherit from this simple structure
|
||||
|
||||
import tables
|
||||
import ../types/objecttype
|
||||
|
||||
|
||||
type
|
||||
Chunk* = ref object
|
||||
## A piece of bytecode.
|
||||
## Consts represents (TODO newdoc)
|
||||
## Code represents (TODO newdoc)
|
||||
## Lines represents (TODO newdoc)
|
||||
consts*: seq[ptr Obj]
|
||||
code*: seq[uint8]
|
||||
lines*: seq[int]
|
|
@ -15,7 +15,7 @@
|
|||
## The module dedicated to the Chunk type
|
||||
## A chunk is a piece of bytecode.
|
||||
|
||||
import ../types/objecttype
|
||||
import ../types/japlvalue
|
||||
|
||||
type
|
||||
OpCode* {.pure.} = enum
|
||||
|
@ -62,16 +62,7 @@ type
|
|||
Bnot
|
||||
|
||||
|
||||
Chunk* = ref object
|
||||
## A piece of bytecode.
|
||||
## Consts is the chunk's constant table
|
||||
## Code contains the compiled bytecode
|
||||
## Lines maps bytecode instructions to lines
|
||||
consts*: seq[ptr Obj]
|
||||
code*: seq[uint8]
|
||||
lines*: seq[int] # TODO: Run-length encoding?
|
||||
|
||||
|
||||
|
||||
const simpleInstructions* = {OpCode.Return, OpCode.Add, OpCode.Multiply,
|
||||
OpCode.Divide, OpCode.Subtract,
|
||||
OpCode.Mod, OpCode.Pow, OpCode.Nil,
|
||||
|
@ -113,15 +104,15 @@ proc freeChunk*(self: Chunk) =
|
|||
self.lines = @[]
|
||||
|
||||
|
||||
proc addConstant*(self: Chunk, constant: ptr Obj): int =
|
||||
proc addConstant*(self: Chunk, constant: Value): int =
|
||||
## Adds a constant to a chunk. Returns its index.
|
||||
self.consts.add(constant)
|
||||
return self.consts.high() # The index of the constant
|
||||
|
||||
|
||||
proc writeConstant*(self: Chunk, constant: ptr Obj): array[3, uint8] =
|
||||
proc writeConstant*(self: Chunk, constant: Value): array[3, uint8] =
|
||||
## Writes a constant to a chunk. Returns its index casted to an array.
|
||||
## TODO newdoc
|
||||
let index = self.addConstant(constant)
|
||||
result = cast[array[3, uint8]](index)
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
import tokentype
|
||||
import valueobject
|
||||
import ../types/japlvalue
|
||||
|
||||
# Token object
|
||||
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# 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.
|
||||
|
||||
## This module represents the generic interface that JAPL uses internally
|
||||
## to represent types. Small-sized entities such as numbers and booleans are
|
||||
## treated differently with respect to bigger and more complex ones such as
|
||||
## strings and functions. That is because those more comolex entities are
|
||||
## allocated on the heap, while the simpler ones live on the stack
|
||||
|
||||
# import ../types/functiontype
|
||||
import japlvalue
|
||||
import ../types/stringtype
|
||||
import strformat
|
||||
|
||||
|
||||
type
|
||||
ValueArray* = ref object
|
||||
values*: seq[Value]
|
||||
|
||||
|
||||
func newValueArray*(): ValueArray =
|
||||
## Creates a new ValueArray
|
||||
result = ValueArray(values: @[])
|
||||
|
||||
|
||||
func writeValueArray*(arr: var ValueArray, value: Value) =
|
||||
## Adds a value to a ValueArray object
|
||||
arr.values.add(value)
|
||||
|
||||
|
||||
func isNil*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nil object
|
||||
result = value.kind == ValueType.Nil
|
||||
|
||||
|
||||
func isBool*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL bool
|
||||
result = value.kind == ValueType.Bool
|
||||
|
||||
|
||||
func isInt*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL integer
|
||||
result = value.kind == ValueType.Integer
|
||||
|
||||
|
||||
func isFloat*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL float
|
||||
result = value.kind == ValueType.Double
|
||||
|
||||
|
||||
func isInf*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL inf object
|
||||
result = value.kind == ValueType.Inf or value.kind == ValueType.Minf
|
||||
|
||||
|
||||
func isNan*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nan object
|
||||
result = value.kind == ValueType.Nan
|
||||
|
||||
|
||||
func isNum*(value: Value): bool =
|
||||
## Returns true if the given value is
|
||||
## either a JAPL number, nan or inf
|
||||
result = isInt(value) or isFloat(value) or isInf(value) or isNan(value)
|
||||
|
||||
|
||||
func isObj*(value: Value): bool =
|
||||
## Returns if the current value is a JAPL object
|
||||
result = value.kind == ValueType.Object
|
||||
|
||||
|
||||
func isStr*(value: Value): bool =
|
||||
## Returns true if the given object is a JAPL string
|
||||
result = isObj(value) and value.obj.kind == ObjectType.String
|
||||
|
||||
|
||||
func toBool*(value: Value): bool =
|
||||
## Converts a JAPL bool to a nim bool
|
||||
result = value.boolValue
|
||||
|
||||
|
||||
func toInt*(value: Value): int =
|
||||
## Converts a JAPL int to a nim int
|
||||
result = value.intValue
|
||||
|
||||
|
||||
func toFloat*(value: Value): float =
|
||||
## Converts a JAPL float to a nim float
|
||||
result = value.floatValue
|
||||
|
||||
|
||||
func toStr*(value: Value): string =
|
||||
## Converts a JAPL string into a nim string
|
||||
var strObj = cast[ptr String](value.obj)
|
||||
for i in 0..strObj.str.len - 1:
|
||||
result.add(strObj.str[i])
|
||||
|
||||
|
||||
func asInt*(n: int): Value =
|
||||
## Creates an int object
|
||||
result = Value(kind: ValueType.Integer, intValue: n)
|
||||
|
||||
|
||||
func asFloat*(n: float): Value =
|
||||
## Creates a float object (double)
|
||||
result = Value(kind: ValueType.Double, floatValue: n)
|
||||
|
||||
|
||||
func asBool*(b: bool): Value =
|
||||
## Creates a boolean object
|
||||
result = Value(kind: ValueType.Bool, boolValue: b)
|
||||
|
||||
|
||||
proc asStr*(s: string): Value =
|
||||
## Creates a string object
|
||||
result = Value(kind: ValueType.Object, obj: newString(s))
|
|
@ -1,150 +0,0 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# 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.
|
||||
|
||||
## This module represents the generic interface that JAPL uses internally
|
||||
## to represent types. Small-sized entities such as numbers and booleans are
|
||||
## treated differently with respect to bigger and more complex ones such as
|
||||
## strings and functions. That is because those more comolex entities are
|
||||
## allocated on the heap, while the simpler ones live on the stack
|
||||
|
||||
# import ../types/functiontype
|
||||
import ../types/objecttype
|
||||
import ../types/stringtype
|
||||
import strformat
|
||||
|
||||
|
||||
type
|
||||
ValueType* {.pure.} = enum
|
||||
# All possible value types (this is the VM's notion of 'type', not the end user's)
|
||||
Integer, Double, Bool, Nil, Object, Nan, Inf, Minf
|
||||
Value* = object
|
||||
## Represents an internal JAPL type
|
||||
case kind*: ValueType
|
||||
of ValueType.Integer:
|
||||
intValue*: int
|
||||
of ValueType.Double:
|
||||
floatValue*: float
|
||||
of ValueType.Bool:
|
||||
boolValue*: bool
|
||||
of ValueType.Nil, ValueType.Inf, ValueType.Nan, ValueType.Minf:
|
||||
discard
|
||||
of ValueType.Object:
|
||||
obj*: ptr Obj
|
||||
|
||||
ValueArray* = ref object
|
||||
values*: seq[Value]
|
||||
|
||||
|
||||
func newValueArray*(): ValueArray =
|
||||
## Creates a new ValueArray
|
||||
result = ValueArray(values: @[])
|
||||
|
||||
|
||||
func writeValueArray*(arr: var ValueArray, value: Value) =
|
||||
## Adds a value to a ValueArray object
|
||||
arr.values.add(value)
|
||||
|
||||
|
||||
func isNil*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nil object
|
||||
result = value.kind == ValueType.Nil
|
||||
|
||||
|
||||
func isBool*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL bool
|
||||
result = value.kind == ValueType.Bool
|
||||
|
||||
|
||||
func isInt*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL integer
|
||||
result = value.kind == ValueType.Integer
|
||||
|
||||
|
||||
func isFloat*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL float
|
||||
result = value.kind == ValueType.Double
|
||||
|
||||
|
||||
func isInf*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL inf object
|
||||
result = value.kind == ValueType.Inf or value.kind == ValueType.Minf
|
||||
|
||||
|
||||
func isNan*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nan object
|
||||
result = value.kind == ValueType.Nan
|
||||
|
||||
|
||||
func isNum*(value: Value): bool =
|
||||
## Returns true if the given value is
|
||||
## either a JAPL number, nan or inf
|
||||
result = isInt(value) or isFloat(value) or isInf(value) or isNan(value)
|
||||
|
||||
|
||||
func isObj*(value: Value): bool =
|
||||
## Returns if the current value is a JAPL object
|
||||
result = value.kind == ValueType.Object
|
||||
|
||||
|
||||
func isStr*(value: Value): bool =
|
||||
## Returns true if the given object is a JAPL string
|
||||
result = isObj(value) and value.obj.kind == ObjectType.String
|
||||
|
||||
|
||||
func toBool*(value: Value): bool =
|
||||
## Converts a JAPL bool to a nim bool
|
||||
result = value.boolValue
|
||||
|
||||
|
||||
func toInt*(value: Value): int =
|
||||
## Converts a JAPL int to a nim int
|
||||
result = value.intValue
|
||||
|
||||
|
||||
func toFloat*(value: Value): float =
|
||||
## Converts a JAPL float to a nim float
|
||||
result = value.floatValue
|
||||
|
||||
|
||||
func toStr*(value: Value): string =
|
||||
## Converts a JAPL string into a nim string
|
||||
var strObj = cast[ptr String](value.obj)
|
||||
for i in 0..strObj.str.len - 1:
|
||||
result.add(strObj.str[i])
|
||||
|
||||
|
||||
func asInt*(n: int): Value =
|
||||
## Creates an int object
|
||||
result = Value(kind: ValueType.Integer, intValue: n)
|
||||
|
||||
|
||||
func asFloat*(n: float): Value =
|
||||
## Creates a float object (double)
|
||||
result = Value(kind: ValueType.Double, floatValue: n)
|
||||
|
||||
|
||||
func asBool*(b: bool): Value =
|
||||
## Creates a boolean object
|
||||
result = Value(kind: ValueType.Bool, boolValue: b)
|
||||
|
||||
|
||||
proc asStr*(s: string): Value =
|
||||
## Creates a string object
|
||||
result = Value(kind: ValueType.Object, obj: newString(s))
|
|
@ -14,8 +14,7 @@
|
|||
|
||||
# WIP - Not working
|
||||
|
||||
import ../meta/valueobject
|
||||
import objecttype
|
||||
import ../meta/japlvalue
|
||||
import exceptions
|
||||
import ../memory
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
## Defines JAPL exceptions
|
||||
|
||||
import objecttype
|
||||
import stringtype
|
||||
import japlvalue
|
||||
import strformat
|
||||
import ../memory
|
||||
|
||||
|
@ -24,6 +24,8 @@ import ../memory
|
|||
proc stringify*(self: ptr JAPLException): string =
|
||||
return &"{self.errName.stringify}: {self.message.stringify}"
|
||||
|
||||
proc isFalsey*(self: ptr JAPLException): bool =
|
||||
return false
|
||||
|
||||
proc newTypeError*(message: string): ptr JAPLException =
|
||||
result = allocateObj(JAPLException, ObjectType.Exception)
|
||||
|
|
|
@ -19,13 +19,11 @@
|
|||
# code objects that can be compiled inside the JAPL runtime, pretty much
|
||||
# like in Python
|
||||
|
||||
import objecttype
|
||||
import stringtype
|
||||
import strformat
|
||||
import ../memory
|
||||
import ../meta/chunk
|
||||
import ../meta/valueobject
|
||||
import tables
|
||||
import ../meta/opcode
|
||||
import japlvalue
|
||||
|
||||
|
||||
type
|
||||
|
@ -43,7 +41,7 @@ proc newFunction*(name: string = "", chunk: Chunk = newChunk(), arity: int = 0):
|
|||
result.chunk = chunk
|
||||
|
||||
|
||||
proc isFalsey*(fn: Function): bool =
|
||||
proc isFalsey*(fn: ptr Function): bool =
|
||||
return false
|
||||
|
||||
|
|
@ -12,14 +12,38 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
## Base structure for objects in JAPL, all
|
||||
## Base structure for values and objects in JAPL, all
|
||||
## types inherit from this simple structure
|
||||
|
||||
import tables
|
||||
import ../meta/japlvalue
|
||||
|
||||
|
||||
type
|
||||
Chunk* = ref object
|
||||
## A piece of bytecode.
|
||||
## Consts represents the constants the code is referring to
|
||||
## Code represents the bytecode
|
||||
## Lines represents which lines the corresponding bytecode was one (1 to 1 correspondence)
|
||||
consts*: seq[Value]
|
||||
code*: seq[uint8]
|
||||
lines*: seq[int]
|
||||
|
||||
ValueType* {.pure.} = enum
|
||||
# All possible value types (this is the VM's notion of 'type', not the end user's)
|
||||
Integer, Double, Bool, Nil, Object, Nan, Inf, Minf
|
||||
Value* = object
|
||||
## Represents an internal JAPL type
|
||||
case kind*: ValueType
|
||||
of ValueType.Integer:
|
||||
intValue*: int
|
||||
of ValueType.Double:
|
||||
floatValue*: float
|
||||
of ValueType.Bool:
|
||||
boolValue*: bool
|
||||
of ValueType.Nil, ValueType.Inf, ValueType.Nan, ValueType.Minf:
|
||||
discard
|
||||
of ValueType.Object:
|
||||
obj*: ptr Obj
|
||||
|
||||
ObjectType* {.pure.} = enum
|
||||
## All the possible object types
|
||||
String, Exception, Function,
|
||||
|
@ -43,7 +67,7 @@ type
|
|||
name*: ptr String
|
||||
arity*: int
|
||||
optionals*: int
|
||||
defaults*: Table[string, Obj]
|
||||
defaults*: Table[string, Value]
|
||||
chunk*: Chunk
|
||||
JAPLException* = object of Obj
|
||||
errName*: ptr String
|
||||
|
@ -90,7 +114,7 @@ proc typeName*(obj: ptr Obj): string =
|
|||
result = "object"
|
||||
|
||||
|
||||
|
||||
# TODO migrate to operations
|
||||
proc bool*(obj: ptr Obj): bool =
|
||||
## Returns wheter the object should
|
||||
## be considered a falsey value
|
||||
|
@ -103,17 +127,17 @@ proc bool*(obj: ptr Obj): bool =
|
|||
else:
|
||||
result = false
|
||||
|
||||
|
||||
# TODO migrate to operations
|
||||
proc eq*(a: ptr Obj, b: ptr Obj): bool =
|
||||
## Compares two objects for equality
|
||||
|
||||
if obj.kind != ObjectType.BaseObject:
|
||||
var newObj = convert obj
|
||||
result = newObj.eq()
|
||||
if a.kind != ObjectType.BaseObject:
|
||||
var newObj = convert(a)
|
||||
result = newObj.eq(b)
|
||||
else:
|
||||
result = a.kind == b.kind
|
||||
|
||||
|
||||
# TODO migrate to operations
|
||||
proc hash*(self: ptr Obj): uint32 =
|
||||
# TODO: Make this actually useful
|
||||
result = 2166136261u32
|
||||
|
@ -171,3 +195,100 @@ proc binaryXor(self, other: ptr Obj): ptr Obj =
|
|||
## Returns the result of self ^ other
|
||||
## or nil if the operation is unsupported
|
||||
result = nil
|
||||
|
||||
|
||||
func isNil*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nil object
|
||||
result = value.kind == ValueType.Nil
|
||||
|
||||
|
||||
func isBool*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL bool
|
||||
result = value.kind == ValueType.Bool
|
||||
|
||||
|
||||
func isInt*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL integer
|
||||
result = value.kind == ValueType.Integer
|
||||
|
||||
|
||||
func isFloat*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL float
|
||||
result = value.kind == ValueType.Double
|
||||
|
||||
|
||||
func isInf*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL inf object
|
||||
result = value.kind == ValueType.Inf or value.kind == ValueType.Minf
|
||||
|
||||
|
||||
func isNan*(value: Value): bool =
|
||||
## Returns true if the given value
|
||||
## is a JAPL nan object
|
||||
result = value.kind == ValueType.Nan
|
||||
|
||||
|
||||
func isNum*(value: Value): bool =
|
||||
## Returns true if the given value is
|
||||
## either a JAPL number, nan or inf
|
||||
result = isInt(value) or isFloat(value) or isInf(value) or isNan(value)
|
||||
|
||||
|
||||
func isObj*(value: Value): bool =
|
||||
## Returns if the current value is a JAPL object
|
||||
result = value.kind == ValueType.Object
|
||||
|
||||
|
||||
func isStr*(value: Value): bool =
|
||||
## Returns true if the given object is a JAPL string
|
||||
result = isObj(value) and value.obj.kind == ObjectType.String
|
||||
|
||||
|
||||
func toBool*(value: Value): bool =
|
||||
## Converts a JAPL bool to a nim bool
|
||||
result = value.boolValue
|
||||
|
||||
|
||||
func toInt*(value: Value): int =
|
||||
## Converts a JAPL int to a nim int
|
||||
result = value.intValue
|
||||
|
||||
|
||||
func toFloat*(value: Value): float =
|
||||
## Converts a JAPL float to a nim float
|
||||
result = value.floatValue
|
||||
|
||||
# TODO ambiguous naming: conflict with toString(value: Value) that does JAPL->JAPL
|
||||
func toStr*(value: Value): string =
|
||||
## Converts a JAPL string into a nim string
|
||||
var strObj = cast[ptr String](value.obj)
|
||||
for i in 0..strObj.str.len - 1:
|
||||
result.add(strObj.str[i])
|
||||
|
||||
|
||||
func asInt*(n: int): Value =
|
||||
## Creates an int object
|
||||
result = Value(kind: ValueType.Integer, intValue: n)
|
||||
|
||||
|
||||
func asFloat*(n: float): Value =
|
||||
## Creates a float object (double)
|
||||
result = Value(kind: ValueType.Double, floatValue: n)
|
||||
|
||||
|
||||
func asBool*(b: bool): Value =
|
||||
## Creates a boolean object
|
||||
result = Value(kind: ValueType.Bool, boolValue: b)
|
||||
|
||||
func asValue*(obj: ptr Obj): Value =
|
||||
## Creates a Value object of ValueType.Object as type and obj (arg 1) as
|
||||
## contained obj
|
||||
|
||||
result = Value(kind: ValueType.Object, obj: obj)
|
||||
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# 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.
|
||||
|
||||
import japlvalue
|
||||
import stringtype
|
||||
import function
|
||||
import exceptions
|
||||
import strutils
|
||||
|
||||
func stringify*(value: Value): string =
|
||||
case value.kind:
|
||||
of ValueType.Integer:
|
||||
result = $value.toInt()
|
||||
of ValueType.Double:
|
||||
result = $value.toFloat()
|
||||
of ValueType.Bool:
|
||||
result = $value.toBool()
|
||||
of ValueType.Nil:
|
||||
result = "nil"
|
||||
of ValueType.Object:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](value.obj).stringify
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](value.obj).stringify
|
||||
else:
|
||||
result = "TODO this was not implemented"
|
||||
of ValueType.Nan:
|
||||
result = "nan"
|
||||
of ValueType.Inf:
|
||||
result = "inf"
|
||||
of ValueType.Minf:
|
||||
result = "-inf"
|
||||
|
||||
func isFalsey*(value: Value): bool =
|
||||
case value.kind:
|
||||
of ValueType.Bool:
|
||||
result = not value.toBool()
|
||||
of ValueType.Object:
|
||||
case value.obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](value.obj).isFalsey()
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](value.obj).isFalsey()
|
||||
of ObjectType.Exception:
|
||||
result = cast[ptr JaplException](value.obj).isFalsey()
|
||||
of ObjectType.Class:
|
||||
result = cast[ptr JaplException](value.obj).isFalsey() # TODO Class
|
||||
of ObjectType.Module:
|
||||
result = cast[ptr JaplException](value.obj).isFalsey() # TODO Module
|
||||
of ObjectType.BaseObject:
|
||||
result = cast[ptr JaplException](value.obj).isFalsey() # TODO BaseObject
|
||||
of ValueType.Integer:
|
||||
result = value.toInt() == 0
|
||||
of ValueType.Double:
|
||||
result = value.toFloat() == 0.0
|
||||
of ValueType.Nil:
|
||||
result = true
|
||||
of ValueType.Inf, ValueType.Minf:
|
||||
result = false
|
||||
of ValueType.Nan:
|
||||
result = true
|
||||
|
||||
func typeName*(obj: ptr Obj): string =
|
||||
case obj.kind:
|
||||
of ObjectType.String:
|
||||
result = cast[ptr String](obj).typeName()
|
||||
of ObjectType.Function:
|
||||
result = cast[ptr Function](obj).typeName()
|
||||
else:
|
||||
result = "" # TODO unimplemented
|
||||
|
||||
func typeName*(value: Value): string =
|
||||
case value.kind:
|
||||
of ValueType.Bool, ValueType.Nil, ValueType.Double,
|
||||
ValueType.Integer, ValueType.Nan, ValueType.Inf:
|
||||
result = ($value.kind).toLowerAscii()
|
||||
of ValueType.Minf:
|
||||
result = "inf"
|
||||
of ValueType.Object:
|
||||
result = typeName(value.obj)
|
||||
|
||||
proc eq*(a: Value, b: Value): bool =
|
||||
if a.kind != b.kind:
|
||||
result = false
|
||||
else:
|
||||
case a.kind:
|
||||
of ValueType.Bool:
|
||||
result = a.toBool() == b.toBool()
|
||||
of ValueType.Nil:
|
||||
result = true
|
||||
of ValueType.Integer:
|
||||
result = a.toInt() == b.toInt()
|
||||
of ValueType.Double:
|
||||
result = a.toFloat() == b.toFloat()
|
||||
of ValueType.Object:
|
||||
case a.obj.kind:
|
||||
of ObjectType.String:
|
||||
var a = cast[ptr String](a.obj)
|
||||
var b = cast[ptr String](b.obj)
|
||||
result = eq(a, b)
|
||||
of ObjectType.Function:
|
||||
var a = cast[ptr Function](a.obj)
|
||||
var b = cast[ptr Function](b.obj)
|
||||
result = eq(a, b)
|
||||
else:
|
||||
result = false # TODO unimplemented
|
||||
|
||||
of ValueType.Inf:
|
||||
result = b.kind == ValueType.Inf
|
||||
of ValueType.Minf:
|
||||
result = b.kind == ValueType.Minf
|
||||
of ValueType.Nan:
|
||||
result = false
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
# therefore immutable from the user's perspective. They are
|
||||
# natively ASCII encoded, but soon they will support for unicode.
|
||||
|
||||
import objecttype
|
||||
import japlvalue
|
||||
import strformat
|
||||
import ../memory
|
||||
|
||||
|
@ -65,3 +65,8 @@ proc newString*(str: string): ptr String =
|
|||
|
||||
proc typeName*(s: ptr String): string =
|
||||
return "string"
|
||||
|
||||
|
||||
proc asStr*(s: string): Value =
|
||||
## Creates a string object
|
||||
result = Value(kind: ValueType.Object, obj: newString(s))
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
## This module takes chunks of bytecode, and prints their contents to the
|
||||
## screen.
|
||||
|
||||
import ../meta/chunk
|
||||
import ../common
|
||||
import ../meta/opcode
|
||||
import ../types/japlvalue
|
||||
import ../types/operations
|
||||
import strformat
|
||||
|
||||
|
||||
|
@ -38,7 +39,7 @@ proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =
|
|||
var constant: int
|
||||
copyMem(constant.addr, unsafeAddr(constantArray), sizeof(constantArray))
|
||||
echo &"\tInstruction at IP: {name}, points to slot {constant}"
|
||||
let obj = chunk.consts.values[constant]
|
||||
let obj = chunk.consts[constant]
|
||||
echo &"\tOperand: {stringify(obj)}\n\tValue kind: {obj.kind}\n"
|
||||
return offset + 4
|
||||
|
||||
|
@ -46,7 +47,7 @@ proc constantLongInstruction(name: string, chunk: Chunk, offset: int): int =
|
|||
proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
|
||||
var constant = chunk.code[offset + 1]
|
||||
echo &"\tInstruction at IP: {name}, points to index {constant}"
|
||||
let obj = chunk.consts.values[constant]
|
||||
let obj = chunk.consts[constant]
|
||||
echo &"\tOperand: {stringify(obj)}\n\tValue kind: {obj.kind}\n"
|
||||
return offset + 2
|
||||
|
||||
|
|
45
vm.nim
45
vm.nim
|
@ -24,12 +24,12 @@ import lenientops
|
|||
import common
|
||||
import compiler
|
||||
import tables
|
||||
import meta/chunk
|
||||
import meta/valueobject
|
||||
import meta/opcode
|
||||
import types/exceptions
|
||||
import types/objecttype
|
||||
import types/japlvalue
|
||||
import types/stringtype
|
||||
import types/functiontype
|
||||
import types/function
|
||||
import types/operations
|
||||
import memory
|
||||
when DEBUG_TRACE_VM:
|
||||
import util/debug
|
||||
|
@ -169,9 +169,9 @@ proc sliceRange(self: var VM): bool =
|
|||
of ObjectType.String:
|
||||
var str = popped.toStr()
|
||||
if sliceEnd.isNil():
|
||||
sliceEnd = Value(kind: INTEGER, intValue: len(str))
|
||||
sliceEnd = Value(kind: ValueType.Integer, intValue: len(str))
|
||||
if sliceStart.isNil():
|
||||
sliceStart = Value(kind: INTEGER, intValue: 0)
|
||||
sliceStart = Value(kind: ValueType.Integer, intValue: 0)
|
||||
elif not sliceStart.isInt() or not sliceEnd.isInt():
|
||||
self.error(newTypeError("string indeces must be integers"))
|
||||
return false
|
||||
|
@ -183,7 +183,7 @@ proc sliceRange(self: var VM): bool =
|
|||
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(""))))
|
||||
return true
|
||||
if sliceEnd.toInt() - 1 > len(str) - 1:
|
||||
sliceEnd = Value(kind: INTEGER, intValue: len(str))
|
||||
sliceEnd = Value(kind: ValueType.Integer, intValue: len(str))
|
||||
if sliceStart.toInt() > sliceEnd.toInt():
|
||||
self.push(Value(kind: OBJECT, obj: addObject(addr self, newString(""))))
|
||||
return true
|
||||
|
@ -252,14 +252,14 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
template readConstant: Value =
|
||||
## Reads a constant from the current
|
||||
## frame's constant table
|
||||
frame.function.chunk.consts.values[int(readByte())]
|
||||
frame.function.chunk.consts[int(readByte())]
|
||||
template readLongConstant: Value =
|
||||
## Reads a long constant from the
|
||||
## current frame's constant table
|
||||
var arr = [readByte(), readByte(), readByte()]
|
||||
var idx: int
|
||||
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
|
||||
frame.function.chunk.consts.values[idx]
|
||||
frame.function.chunk.consts[idx]
|
||||
template binOp(op, check) =
|
||||
## Performs binary operations on types,
|
||||
## this will be soon ditched in favor
|
||||
|
@ -332,7 +332,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
var rightVal {.inject.} = self.pop()
|
||||
var leftVal {.inject.} = self.pop()
|
||||
if isInt(leftVal) and isInt(rightVal):
|
||||
self.push(Value(kind: INTEGER, intValue: `op`(leftVal.toInt(), rightVal.toInt())))
|
||||
self.push(Value(kind: ValueType.Integer, intValue: `op`(leftVal.toInt(), rightVal.toInt())))
|
||||
else:
|
||||
self.error(newTypeError(&"unsupported binary operator for objects of type '{leftVal.typeName()}' and '{rightVal.typeName()}'"))
|
||||
return RUNTIME_ERROR
|
||||
|
@ -340,7 +340,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
## Handles unary bitwise operators
|
||||
var leftVal {.inject.} = self.pop()
|
||||
if isInt(leftVal):
|
||||
self.push(Value(kind: INTEGER, intValue: `op`(leftVal.toInt())))
|
||||
self.push(Value(kind: ValueType.Integer, intValue: `op`(leftVal.toInt())))
|
||||
else:
|
||||
self.error(newTypeError(&"unsupported unary operator for object of type '{leftVal.typeName()}'"))
|
||||
return RUNTIME_ERROR
|
||||
|
@ -466,15 +466,15 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
of OpCode.Inf:
|
||||
self.push(Value(kind: ValueType.Inf))
|
||||
of OpCode.Not:
|
||||
self.push(Value(kind: BOOL, boolValue: isFalsey(self.pop())))
|
||||
self.push(Value(kind: ValueType.Bool, boolValue: isFalsey(self.pop())))
|
||||
of OpCode.Equal:
|
||||
var a = self.pop()
|
||||
var b = self.pop()
|
||||
if a.isFloat() and b.isInt():
|
||||
b = Value(kind: DOUBLE, floatValue: float b.toInt())
|
||||
b = Value(kind: ValueType.Double, floatValue: float b.toInt())
|
||||
elif b.isFloat() and a.isInt():
|
||||
a = Value(kind: DOUBLE, floatValue: float a.toInt())
|
||||
self.push(Value(kind: BOOL, boolValue: valuesEqual(a, b)))
|
||||
a = Value(kind: ValueType.Double, floatValue: float a.toInt())
|
||||
self.push(Value(kind: ValueType.Bool, boolValue: eq(a, b)))
|
||||
of OpCode.Less:
|
||||
binOp(`<`, isNum)
|
||||
of OpCode.Greater:
|
||||
|
@ -486,7 +486,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
if not self.sliceRange():
|
||||
return RUNTIME_ERROR
|
||||
of OpCode.DefineGlobal:
|
||||
if frame.function.chunk.consts.values.len > 255:
|
||||
if frame.function.chunk.consts.len > 255:
|
||||
var constant = readLongConstant().toStr()
|
||||
self.globals[constant] = self.peek(0)
|
||||
else:
|
||||
|
@ -494,7 +494,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
self.globals[constant] = self.peek(0)
|
||||
discard self.pop() # This will help when we have a custom GC
|
||||
of OpCode.GetGlobal:
|
||||
if frame.function.chunk.consts.values.len > 255:
|
||||
if frame.function.chunk.consts.len > 255:
|
||||
var constant = readLongConstant().toStr()
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||
|
@ -509,7 +509,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
else:
|
||||
self.push(self.globals[constant])
|
||||
of OpCode.SetGlobal:
|
||||
if frame.function.chunk.consts.values.len > 255:
|
||||
if frame.function.chunk.consts.len > 255:
|
||||
var constant = readLongConstant().toStr()
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
||||
|
@ -525,7 +525,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
|||
self.globals[constant] = self.peek(0)
|
||||
of OpCode.DeleteGlobal:
|
||||
# This OpCode, as well as DeleteLocal, is currently unused due to potential issues with the GC
|
||||
if frame.function.chunk.consts.values.len > 255:
|
||||
if frame.function.chunk.consts.len > 255:
|
||||
var constant = readLongConstant().toStr()
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||
|
@ -643,7 +643,8 @@ proc freeVM*(self: var VM) =
|
|||
proc initVM*(): VM =
|
||||
## Initializes the VM
|
||||
setControlCHook(handleInterrupt)
|
||||
result = VM(lastPop: Value(kind: ValueType.Nil), objects: @[], globals: initTable[string, Value](), source: "", file: "")
|
||||
var globals: Table[string, Value] = initTable[string, Value]()
|
||||
result = VM(lastPop: Value(kind: ValueType.Nil), objects: @[], globals: globals, source: "", file: "")
|
||||
# TODO asNil() ?
|
||||
|
||||
|
||||
|
@ -659,8 +660,8 @@ proc interpret*(self: var VM, source: string, repl: bool = false, file: string):
|
|||
# to the vm
|
||||
if compiled == nil:
|
||||
return COMPILE_ERROR
|
||||
self.push(Value(kind: OBJECT, obj: compiled))
|
||||
discard self.callValue(Value(kind: OBJECT, obj: compiled), 0)
|
||||
self.push(Value(kind: ValueType.Object, obj: compiled))
|
||||
discard self.callValue(Value(kind: ValueType.Object, obj: compiled), 0)
|
||||
when DEBUG_TRACE_VM:
|
||||
echo "==== VM debugger starts ====\n"
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue