Removed old JAPL type system. Initial dummy VM for testing purposes
This commit is contained in:
parent
0e4eb82554
commit
223ba603a7
|
@ -1,196 +0,0 @@
|
|||
# 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.
|
||||
|
||||
# Implementation of a custom list data type for JAPL objects (used also internally by the VM)
|
||||
|
||||
import iterable
|
||||
import ../../memory/allocator
|
||||
import baseObject
|
||||
|
||||
import strformat
|
||||
|
||||
|
||||
type
|
||||
ArrayList*[T] = object of Iterable
|
||||
## Implementation of a simple dynamic
|
||||
## array with amortized O(1) append complexity
|
||||
## and O(1) complexity when popping/deleting
|
||||
## the last element
|
||||
container: ptr UncheckedArray[T]
|
||||
ArrayListIterator*[T] = object of Iterator
|
||||
list: ArrayList[T]
|
||||
current: int
|
||||
|
||||
|
||||
proc newArrayList*[T]: ptr ArrayList[T] =
|
||||
## Allocates a new, empty array list
|
||||
result = allocateObj(ArrayList[T], ObjectType.List)
|
||||
result.capacity = 0
|
||||
result.container = nil
|
||||
result.length = 0
|
||||
|
||||
|
||||
proc append*[T](self: ptr ArrayList[T], elem: T) =
|
||||
## Appends an object to the end of the list
|
||||
## in amortized constant time (~O(1))
|
||||
if self.capacity <= self.length:
|
||||
self.capacity = growCapacity(self.capacity)
|
||||
self.container = resizeArray(T, self.container, self.length, self.capacity)
|
||||
self.container[self.length] = elem
|
||||
self.length += 1
|
||||
|
||||
|
||||
proc pop*[T](self: ptr ArrayList[T], idx: int = -1): T =
|
||||
## Pops an item from the list. By default, the last
|
||||
## element is popped, in which case the operation's
|
||||
## time complexity is O(1). When an arbitrary element
|
||||
## is popped, the complexity rises to O(k) where k
|
||||
## is the number of elements that had to be shifted
|
||||
## by 1 to avoid empty slots
|
||||
var idx = idx
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, "pop from empty ArrayList")
|
||||
if idx == -1:
|
||||
idx = self.length - 1
|
||||
if idx notin 0..self.length - 1:
|
||||
raise newException(IndexDefect, &"ArrayList index out of bounds: {idx} notin 0..{self.length - 1}")
|
||||
result = self.container[idx]
|
||||
if idx != self.length - 1:
|
||||
for i in countup(idx, self.length - 1):
|
||||
self.container[i] = self.container[i + 1]
|
||||
self.capacity -= 1
|
||||
self.length -= 1
|
||||
|
||||
|
||||
proc `[]`*[T](self: ptr ArrayList[T], idx: int): T =
|
||||
## Retrieves an item from the list, in constant
|
||||
## time
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, &"ArrayList index out of bounds: : {idx} notin 0..{self.length - 1}")
|
||||
if idx notin 0..self.length - 1:
|
||||
raise newException(IndexDefect, &"ArrayList index out of bounds: {idx} notin 0..{self.length - 1}")
|
||||
result = self.container[idx]
|
||||
|
||||
|
||||
proc `[]`*[T](self: ptr ArrayList[T], slice: Hslice[int, int]): ptr ArrayList[T] =
|
||||
## Retrieves a subset of the list, in O(k) time where k is the size
|
||||
## of the slice
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, "ArrayList index out of bounds")
|
||||
if slice.a notin 0..self.length - 1 or slice.b notin 0..self.length:
|
||||
raise newException(IndexDefect, "ArrayList index out of bounds")
|
||||
result = newArrayList[T]()
|
||||
for i in countup(slice.a, slice.b - 1):
|
||||
result.append(self.container[i])
|
||||
|
||||
|
||||
proc `[]=`*[T](self: ptr ArrayList[T], idx: int, obj: T) =
|
||||
## Assigns an object to the given index, in constant
|
||||
## time
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, "ArrayList is empty")
|
||||
if idx notin 0..self.length - 1:
|
||||
raise newException(IndexDefect, "ArrayList index out of bounds")
|
||||
self.container[idx] = obj
|
||||
|
||||
|
||||
proc delete*[T](self: ptr ArrayList[T], idx: int) =
|
||||
## Deletes an object from the given index.
|
||||
## This method shares the time complexity
|
||||
## of self.pop()
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, "delete from empty ArrayList")
|
||||
if idx notin 0..self.length - 1:
|
||||
raise newException(IndexDefect, &"ArrayList index out of bounds: {idx} notin 0..{self.length - 1}")
|
||||
discard self.pop(idx)
|
||||
|
||||
|
||||
proc contains*[T](self: ptr ArrayList[T], elem: T): bool =
|
||||
## Returns true if the given object is present
|
||||
## in the list, false otherwise. O(n) complexity
|
||||
if self.length > 0:
|
||||
for i in 0..self.length - 1:
|
||||
if self[i] == elem:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
proc high*[T](self: ptr ArrayList[T]): int =
|
||||
## Returns the index of the last
|
||||
## element in the list, in constant time
|
||||
if self.length == 0:
|
||||
raise newException(IndexDefect, "ArrayList is empty")
|
||||
result = self.length - 1
|
||||
|
||||
|
||||
proc len*[T](self: ptr ArrayList[T]): int =
|
||||
## Returns the length of the list
|
||||
## in constant time
|
||||
result = self.length
|
||||
|
||||
|
||||
iterator pairs*[T](self: ptr ArrayList[T]): tuple[key: int, val: T] =
|
||||
## Implements pairwise iteration (similar to python's enumerate)
|
||||
for i in countup(0, self.length - 1):
|
||||
yield (key: i, val: self[i])
|
||||
|
||||
|
||||
iterator items*[T](self: ptr ArrayList[T]): T =
|
||||
## Implements iteration
|
||||
for i in countup(0, self.length - 1):
|
||||
yield self[i]
|
||||
|
||||
|
||||
proc reversed*[T](self: ptr ArrayList[T], first: int = -1, last: int = 0): ptr ArrayList[T] =
|
||||
## Returns a reversed version of the given list, from first to last.
|
||||
## First defaults to -1 (the end of the list) and last defaults to 0 (the
|
||||
## beginning of the list)
|
||||
var first = first
|
||||
if first == -1:
|
||||
first = self.length - 1
|
||||
result = newArrayList[T]()
|
||||
for i in countdown(first, last):
|
||||
result.append(self[i])
|
||||
|
||||
|
||||
proc extend*[T](self: ptr ArrayList[T], other: seq[T]) =
|
||||
## Iteratively calls self.append() with the elements
|
||||
## from a nim sequence
|
||||
for elem in other:
|
||||
self.append(elem)
|
||||
|
||||
|
||||
proc extend*[T](self: ptr ArrayList[T], other: ptr ArrayList[T]) =
|
||||
## Iteratively calls self.append() with the elements
|
||||
## from another ArrayList
|
||||
for elem in other:
|
||||
self.append(elem)
|
||||
|
||||
|
||||
proc `$`*[T](self: ptr ArrayList[T]): string =
|
||||
## Returns a string representation
|
||||
## of self
|
||||
result = "["
|
||||
if self.length > 0:
|
||||
for i in 0..self.length - 1:
|
||||
result = result & $self.container[i]
|
||||
if i < self.length - 1:
|
||||
result = result & ", "
|
||||
result = result & "]"
|
||||
|
||||
|
||||
proc getIter*[T](self: ptr ArrayList[T]): Iterator =
|
||||
## Returns the iterator object of the
|
||||
## arraylist
|
||||
result = allocate(ArrayListIterator, ) # TODO
|
|
@ -1,84 +0,0 @@
|
|||
# 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.
|
||||
|
||||
|
||||
## The base JAPL object
|
||||
|
||||
import ../../memory/allocator
|
||||
|
||||
|
||||
type
|
||||
ObjectType* {.pure.} = enum
|
||||
## All the possible object types
|
||||
String, Exception, Function,
|
||||
Class, Module, BaseObject,
|
||||
Native, Integer, Float,
|
||||
Bool, NotANumber, Infinity,
|
||||
Nil, List, Dict, Set, Tuple
|
||||
Obj* = object of RootObj
|
||||
## The base object for all
|
||||
## JAPL types. Every object
|
||||
## in JAPL implicitly inherits
|
||||
## from this base type and extends
|
||||
## its functionality
|
||||
kind*: ObjectType
|
||||
hashValue*: uint64
|
||||
|
||||
|
||||
## Object constructors and allocators
|
||||
|
||||
proc allocateObject*(size: int, kind: ObjectType): ptr Obj =
|
||||
## Wrapper around reallocate() to create a new generic JAPL object
|
||||
result = cast[ptr Obj](reallocate(nil, 0, size))
|
||||
result.kind = kind
|
||||
|
||||
|
||||
template allocateObj*(kind: untyped, objType: ObjectType): untyped =
|
||||
## Wrapper around allocateObject to cast a generic object
|
||||
## to a more specific type
|
||||
cast[ptr kind](allocateObject(sizeof kind, objType))
|
||||
|
||||
|
||||
proc newObj*: ptr Obj =
|
||||
## Allocates a generic JAPL object
|
||||
result = allocateObj(Obj, ObjectType.BaseObject)
|
||||
result.hashValue = 0x123FFFF
|
||||
|
||||
|
||||
## Default object methods implementations
|
||||
|
||||
# In JAPL code, this method will be called
|
||||
# stringify()
|
||||
proc `$`*(self: ptr Obj): string = "<object>"
|
||||
proc stringify*(self: ptr Obj): string = $self
|
||||
|
||||
proc hash*(self: ptr Obj): int64 = 0x123FFAA # Constant hash value
|
||||
# I could've used mul, sub and div, but "div" is a reserved
|
||||
# keyword and using `div` looks ugly. So to keep everything
|
||||
# consistent I just made all names long
|
||||
proc multiply*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc sum*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc divide*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc subtract*(self, other: ptr Obj): ptr Obj = nil
|
||||
# Returns 0 if self == other, a negative number if self < other
|
||||
# and a positive number if self > other. This is a convenience
|
||||
# method to implement all basic comparison operators in one
|
||||
# method
|
||||
proc compare*(self, other: ptr Obj): ptr Obj = nil
|
||||
# Specific methods for each comparison
|
||||
proc equalTo*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc greaterThan*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc lessThan*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc greaterOrEqual*(self, other: ptr Obj): ptr Obj = nil
|
||||
proc lessOrEqual*(self, other: ptr Obj): ptr Obj = nil
|
|
@ -1,48 +0,0 @@
|
|||
# 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.
|
||||
|
||||
## Type dispatching module
|
||||
import baseObject
|
||||
import intObject
|
||||
import floatObject
|
||||
|
||||
|
||||
proc dispatch*(obj: ptr Obj, p: proc (self: ptr Obj): ptr Obj): ptr Obj =
|
||||
## Dispatches a given one-argument procedure according to
|
||||
## the provided object's runtime type and returns its result
|
||||
case obj.kind:
|
||||
of BaseObject:
|
||||
result = p(obj)
|
||||
of ObjectType.Float:
|
||||
result = p(cast[ptr Float](obj))
|
||||
of ObjectType.Integer:
|
||||
result = p(cast[ptr Integer](obj))
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc dispatch*(a, b: ptr Obj, p: proc (self: ptr Obj, other: ptr Obj): ptr Obj): ptr Obj =
|
||||
## Dispatches a given two-argument procedure according to
|
||||
## the provided object's runtime type and returns its result
|
||||
case a.kind:
|
||||
of BaseObject:
|
||||
result = p(a, b)
|
||||
of ObjectType.Float:
|
||||
# Further type casting for b is expected to occur later
|
||||
# in the given procedure
|
||||
result = p(cast[ptr Float](a), b)
|
||||
of ObjectType.Integer:
|
||||
result = p(cast[ptr Integer](a), b)
|
||||
else:
|
||||
discard
|
|
@ -1,49 +0,0 @@
|
|||
# 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.
|
||||
|
||||
## Implementation of integer types
|
||||
|
||||
import baseObject
|
||||
import lenientops
|
||||
|
||||
|
||||
type Float* = object of Obj
|
||||
value: float64
|
||||
|
||||
|
||||
proc newFloat*(value: float): ptr Float =
|
||||
## Initializes a new JAPL
|
||||
## float object from
|
||||
## a machine native float
|
||||
result = allocateObj(Float, ObjectType.Float)
|
||||
result.value = value
|
||||
|
||||
|
||||
proc toNativeFloat*(self: ptr Float): float =
|
||||
## Returns the float's machine
|
||||
## native underlying value
|
||||
result = self.value
|
||||
|
||||
|
||||
proc `$`*(self: ptr Float): string = $self.value
|
||||
|
||||
|
||||
proc hash*(self: ptr Float): int64 =
|
||||
## Implements hashing
|
||||
## for the given float
|
||||
if self.value - int(self.value) == self.value:
|
||||
result = int(self.value)
|
||||
else:
|
||||
result = 2166136261 xor int(self.value) # TODO: Improve this
|
||||
result *= 16777619
|
|
@ -1,207 +0,0 @@
|
|||
# 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.
|
||||
|
||||
|
||||
import ../../memory/allocator
|
||||
import ../../config
|
||||
|
||||
import baseObject
|
||||
import iterable
|
||||
|
||||
|
||||
type
|
||||
Entry = object
|
||||
## Low-level object to store key/value pairs.
|
||||
## Using an extra value for marking the entry as
|
||||
## a tombstone instead of something like detecting
|
||||
## tombstones as entries with null keys but full values
|
||||
## may seem wasteful. The thing is, though, that since
|
||||
## we want to implement sets on top of this hashmap and
|
||||
## the implementation of a set is *literally* a dictionary
|
||||
## with empty values and keys as the elements, this would
|
||||
## confuse our findEntry method and would force us to override
|
||||
## it to account for a different behavior.
|
||||
## Using a third field takes up more space, but saves us
|
||||
## from the hassle of rewriting code
|
||||
key: ptr Obj
|
||||
value: ptr Obj
|
||||
tombstone: bool
|
||||
HashMap* = object of Iterable
|
||||
## An associative array with O(1) lookup time,
|
||||
## similar to nim's Table type, but using raw
|
||||
## memory to be more compatible with JAPL's runtime
|
||||
## memory management
|
||||
entries: ptr UncheckedArray[ptr Entry]
|
||||
# This attribute counts *only* non-deleted entries
|
||||
actual_length: int
|
||||
|
||||
|
||||
proc newHashMap*: ptr HashMap =
|
||||
## Initializes a new, empty hashmap
|
||||
result = allocateObj(HashMap, ObjectType.Dict)
|
||||
result.actual_length = 0
|
||||
result.entries = nil
|
||||
result.capacity = 0
|
||||
result.length = 0
|
||||
|
||||
|
||||
proc freeHashMap*(self: ptr HashMap) =
|
||||
## Frees the memory associated with the hashmap
|
||||
discard freeArray(UncheckedArray[ptr Entry], self.entries, self.capacity)
|
||||
self.length = 0
|
||||
self.actual_length = 0
|
||||
self.capacity = 0
|
||||
self.entries = nil
|
||||
|
||||
|
||||
proc findEntry(self: ptr UncheckedArray[ptr Entry], key: ptr Obj, capacity: int): ptr Entry =
|
||||
## Low-level method used to find entries in the underlying
|
||||
## array, returns a pointer to an entry
|
||||
var capacity = uint64(capacity)
|
||||
var idx = uint64(key.hash()) mod capacity
|
||||
while true:
|
||||
result = self[idx]
|
||||
if system.`==`(result.key, nil):
|
||||
# We found an empty bucket
|
||||
break
|
||||
elif result.tombstone:
|
||||
# We found a previously deleted
|
||||
# entry. In this case, we need
|
||||
# to make sure the tombstone
|
||||
# will get overwritten when the
|
||||
# user wants to add a new value
|
||||
# that would replace it, BUT also
|
||||
# for it to not stop our linear
|
||||
# probe sequence. Hence, if the
|
||||
# key of the tombstone is the same
|
||||
# as the one we're looking for,
|
||||
# we break out of the loop, otherwise
|
||||
# we keep searching
|
||||
if result.key == key:
|
||||
break
|
||||
elif result.key == key:
|
||||
# We were looking for a specific key and
|
||||
# we found it, so we also bail out
|
||||
break
|
||||
# If none of these conditions match, we have a collision!
|
||||
# This means we can just move on to the next slot in our probe
|
||||
# sequence until we find an empty slot. The way our resizing
|
||||
# mechanism works makes the empty slot invariant easy to
|
||||
# maintain since we increase the underlying array's size
|
||||
# before we are actually full
|
||||
idx = (idx + 1) mod capacity
|
||||
|
||||
|
||||
proc adjustCapacity(self: ptr HashMap) =
|
||||
var newCapacity = growCapacity(self.capacity)
|
||||
var entries = allocate(UncheckedArray[ptr Entry], Entry, newCapacity)
|
||||
var oldEntry: ptr Entry
|
||||
var newEntry: ptr Entry
|
||||
self.length = 0
|
||||
for x in countup(0, newCapacity - 1):
|
||||
entries[x] = allocate(Entry, Entry, 1)
|
||||
entries[x].tombstone = false
|
||||
entries[x].key = nil
|
||||
entries[x].value = nil
|
||||
for x in countup(0, self.capacity - 1):
|
||||
oldEntry = self.entries[x]
|
||||
if not system.`==`(oldEntry.key, nil):
|
||||
newEntry = entries.findEntry(oldEntry.key, newCapacity)
|
||||
newEntry.key = oldEntry.key
|
||||
newEntry.value = oldEntry.value
|
||||
self.length += 1
|
||||
discard freeArray(UncheckedArray[ptr Entry], self.entries, self.capacity)
|
||||
self.entries = entries
|
||||
self.capacity = newCapacity
|
||||
|
||||
|
||||
proc setEntry(self: ptr HashMap, key: ptr Obj, value: ptr Obj): bool =
|
||||
if float64(self.length + 1) >= float64(self.capacity) * MAP_LOAD_FACTOR:
|
||||
self.adjustCapacity()
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
result = system.`==`(entry.key, nil)
|
||||
if result:
|
||||
self.actual_length += 1
|
||||
self.length += 1
|
||||
entry.key = key
|
||||
entry.value = value
|
||||
entry.tombstone = false
|
||||
|
||||
|
||||
proc `[]`*(self: ptr HashMap, key: ptr Obj): ptr Obj =
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
if system.`==`(entry.key, nil) or entry.tombstone:
|
||||
raise newException(KeyError, "Key not found: " & $key)
|
||||
result = entry.value
|
||||
|
||||
|
||||
proc `[]=`*(self: ptr HashMap, key: ptr Obj, value: ptr Obj) =
|
||||
discard self.setEntry(key, value)
|
||||
|
||||
|
||||
proc len*(self: ptr HashMap): int =
|
||||
result = self.actual_length
|
||||
|
||||
|
||||
proc del*(self: ptr HashMap, key: ptr Obj) =
|
||||
if self.len() == 0:
|
||||
raise newException(KeyError, "delete from empty hashmap")
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
if not system.`==`(entry.key, nil):
|
||||
self.actual_length -= 1
|
||||
entry.tombstone = true
|
||||
else:
|
||||
raise newException(KeyError, "Key not found: " & $key)
|
||||
|
||||
|
||||
proc contains*(self: ptr HashMap, key: ptr Obj): bool =
|
||||
let entry = findEntry(self.entries, key, self.capacity)
|
||||
if not system.`==`(entry.key, nil) and not entry.tombstone:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
|
||||
|
||||
iterator keys*(self: ptr HashMap): ptr Obj =
|
||||
var entry: ptr Entry
|
||||
for i in countup(0, self.capacity - 1):
|
||||
entry = self.entries[i]
|
||||
if not system.`==`(entry.key, nil) and not entry.tombstone:
|
||||
yield entry.key
|
||||
|
||||
|
||||
iterator values*(self: ptr HashMap): ptr Obj =
|
||||
for key in self.keys():
|
||||
yield self[key]
|
||||
|
||||
|
||||
iterator pairs*(self: ptr HashMap): tuple[key: ptr Obj, val: ptr Obj] =
|
||||
for key in self.keys():
|
||||
yield (key: key, val: self[key])
|
||||
|
||||
|
||||
iterator items*(self: ptr HashMap): ptr Obj =
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
|
||||
proc `$`*(self: ptr HashMap): string =
|
||||
var i = 0
|
||||
result &= "{"
|
||||
for key, value in self.pairs():
|
||||
result &= $key & ": " & $value
|
||||
if i < self.len() - 1:
|
||||
result &= ", "
|
||||
i += 1
|
||||
result &= "}"
|
|
@ -1,40 +0,0 @@
|
|||
# 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.
|
||||
|
||||
## Implementation of integer types
|
||||
|
||||
import baseObject
|
||||
|
||||
|
||||
type Integer* = object of Obj
|
||||
value: int64
|
||||
|
||||
|
||||
proc newInteger*(value: int64): ptr Integer =
|
||||
## Initializes a new JAPL
|
||||
## integer object from
|
||||
## a machine native integer
|
||||
result = allocateObj(Integer, ObjectType.Integer)
|
||||
result.value = value
|
||||
|
||||
|
||||
proc toNativeInteger*(self: ptr Integer): int64 =
|
||||
## Returns the integer's machine
|
||||
## native underlying value
|
||||
result = self.value
|
||||
|
||||
|
||||
proc `$`*(self: ptr Integer): string = $self.value
|
||||
proc hash*(self: ptr Integer): int64 = self.value
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# 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.
|
||||
|
||||
# Implementation of iterable types and iterators in JAPL
|
||||
|
||||
import baseObject
|
||||
|
||||
|
||||
type
|
||||
Iterable* = object of Obj
|
||||
## Defines the standard interface
|
||||
## for iterable types in JAPL
|
||||
length*: int
|
||||
capacity*: int
|
||||
Iterator* = object of Iterable
|
||||
## This object drives iteration
|
||||
## for every iterable type in JAPL except
|
||||
## generators
|
||||
iterable*: ptr Obj
|
||||
iterCount*: int
|
||||
|
||||
|
||||
proc getIter*(self: Iterable): ptr Iterator =
|
||||
## Returns the iterator object of an
|
||||
## iterable, which drives foreach
|
||||
## loops
|
||||
return nil
|
||||
|
||||
|
||||
proc next*(self: Iterator): ptr Obj =
|
||||
## Returns the next element from
|
||||
## the iterator or nil if the
|
||||
## iterator has been consumed
|
||||
return nil
|
|
@ -1,15 +0,0 @@
|
|||
# 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.
|
||||
|
||||
# JAPL string implementations
|
|
@ -12,9 +12,106 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
## The JAPL runtime environment
|
||||
## The Peon runtime environment
|
||||
import types
|
||||
import ../config
|
||||
import ../frontend/meta/bytecode
|
||||
|
||||
|
||||
type
|
||||
VM* = ref object
|
||||
stack:
|
||||
PeonVM* = ref object
|
||||
## The Peon Virtual Machine
|
||||
stack: seq[PeonObject]
|
||||
ip: int # Instruction pointer
|
||||
sp: int # Stack pointer
|
||||
cache: array[6, PeonObject] # Singletons cache
|
||||
chunk: Chunk # Piece of bytecode to execute
|
||||
|
||||
|
||||
proc newPeonVM*: PeonVM =
|
||||
## Initializes a new, blank VM
|
||||
## for executing Peon bytecode
|
||||
new(result)
|
||||
result.ip = 0
|
||||
result.sp = 0
|
||||
result.stack = newSeqOfCap[PeonObject](INITIAL_STACK_SIZE)
|
||||
result.cache[0] = newNil()
|
||||
result.cache[1] = newBool(true)
|
||||
result.cache[2] = newBool(false)
|
||||
result.cache[3] = newInf(true)
|
||||
result.cache[4] = newInf(false)
|
||||
result.cache[5] = newNan()
|
||||
for _ in 0..<INITIAL_STACK_SIZE:
|
||||
result.stack.add(result.cache[0])
|
||||
|
||||
|
||||
proc getNil*(self: PeonVM): Nil = Nil(self.cache[0])
|
||||
|
||||
proc getBool*(self: PeonVM, value: bool): Bool =
|
||||
if value:
|
||||
return Bool(self.cache[1])
|
||||
return Bool(self.cache[2])
|
||||
|
||||
proc getInf*(self: PeonVM, positive: bool): Inf =
|
||||
if positive:
|
||||
return types.Inf(self.cache[3])
|
||||
return types.Inf(self.cache[4])
|
||||
|
||||
proc getNan*(self: PeonVM): Nan = types.Nan(self.cache[5])
|
||||
|
||||
|
||||
proc push(self: PeonVM, obj: PeonObject) =
|
||||
if self.sp >= self.stack.high():
|
||||
for _ in 0..self.stack.len():
|
||||
self.stack.add(newNil())
|
||||
self.stack[self.sp] = obj
|
||||
inc(self.sp)
|
||||
|
||||
|
||||
proc pop(self: PeonVM): PeonObject =
|
||||
dec(self.sp)
|
||||
return self.stack[self.sp]
|
||||
|
||||
|
||||
proc readByte(self: PeonVM, chunk: Chunk): uint8 =
|
||||
inc(self.ip)
|
||||
return chunk.code[self.ip - 1]
|
||||
|
||||
|
||||
proc dispatch*(self: PeonVM) =
|
||||
## Main bytecode dispatch loop
|
||||
var instruction: OpCode
|
||||
while true:
|
||||
stdout.write("[")
|
||||
for i, e in self.stack:
|
||||
stdout.write($(e[]))
|
||||
if i < self.stack.high():
|
||||
stdout.write(", ")
|
||||
echo "]"
|
||||
instruction = OpCode(self.readByte(self.chunk))
|
||||
echo instruction
|
||||
case instruction:
|
||||
of OpCode.True:
|
||||
self.push(self.getBool(true))
|
||||
of OpCode.False:
|
||||
self.push(self.getBool(false))
|
||||
of OpCode.Nan:
|
||||
self.push(self.getNan())
|
||||
of OpCode.Nil:
|
||||
self.push(self.getNil())
|
||||
of OpCode.Inf:
|
||||
self.push(self.getInf(true))
|
||||
of UnaryPlus:
|
||||
self.push(self.pop())
|
||||
of OpCode.Return:
|
||||
return
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc run*(self: PeonVM, chunk: Chunk) =
|
||||
## Executes a piece of Peon bytecode.
|
||||
self.chunk = chunk
|
||||
self.sp = 0
|
||||
self.ip = 0
|
||||
self.dispatch()
|
||||
|
|
|
@ -37,6 +37,7 @@ const SKIP_STDLIB_INIT* = false # Skips stdlib initialization (can be imported
|
|||
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
|
||||
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
|
||||
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
|
||||
const INITIAL_STACK_SIZE* = 0
|
||||
const PEON_VERSION_STRING* = &"Peon {PEON_VERSION.major}.{PEON_VERSION.minor}.{PEON_VERSION.patch} {PEON_RELEASE} ({PEON_BRANCH}, {CompileDate}, {CompileTime}, {PEON_COMMIT_HASH[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
|
||||
const HELP_MESSAGE* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
|
||||
|
||||
|
|
|
@ -831,6 +831,14 @@ proc inferValueType(self: Compiler, node: ASTNode): ASTNode =
|
|||
return newIdentExpr(Token(lexeme: "int"))
|
||||
of nilExpr:
|
||||
return newIdentExpr(Token(lexeme: "nil"))
|
||||
of trueExpr:
|
||||
return newIdentExpr(Token(lexeme: "true"))
|
||||
of falseExpr:
|
||||
return newIdentExpr(Token(lexeme: "false"))
|
||||
of nanExpr:
|
||||
return newIdentExpr(Token(lexeme: "nan"))
|
||||
of infExpr:
|
||||
return newIdentExpr(Token(lexeme: "inf"))
|
||||
else:
|
||||
discard # TODO
|
||||
|
||||
|
|
|
@ -500,6 +500,19 @@ proc parseNumber(self: Lexer) =
|
|||
self.tokens[^1].lexeme = "0b" & "0" & self.tokens[^1].lexeme[2..^1]
|
||||
|
||||
|
||||
proc parseBackticks(self: Lexer) =
|
||||
## Parses tokens surrounded
|
||||
## by backticks. This may be used
|
||||
## for name stropping as well as to
|
||||
## reimplement existing operators
|
||||
## (e.g. +, -, etc.)
|
||||
|
||||
while not self.match("`") and not self.done():
|
||||
discard self.step()
|
||||
self.createToken(CustomOperator)
|
||||
# Strips the backticks
|
||||
self.tokens[^1].lexeme = self.tokens[^1].lexeme[1..^2]
|
||||
|
||||
|
||||
proc parseIdentifier(self: Lexer) =
|
||||
## Parses keywords and identifiers.
|
||||
|
@ -528,12 +541,17 @@ proc next(self: Lexer) =
|
|||
# We skip characters we don't need
|
||||
return
|
||||
elif self.match(" "):
|
||||
# Whitespaces
|
||||
self.createToken(TokenType.Whitespace)
|
||||
elif self.match("\r"):
|
||||
# Tabs
|
||||
self.createToken(TokenType.Tab)
|
||||
elif self.match("\n"):
|
||||
# New line
|
||||
self.incLine()
|
||||
elif self.match("`"):
|
||||
# Stropped token
|
||||
self.parseBackticks()
|
||||
elif self.match(["\"", "'"]):
|
||||
# String or character literal
|
||||
var mode = "single"
|
||||
|
@ -559,7 +577,7 @@ proc next(self: Lexer) =
|
|||
else:
|
||||
self.error(&"unknown string prefix '{self.peek(-1)}'")
|
||||
elif self.peek().isAlphaNumeric() or self.check("_"):
|
||||
# Tries to match keywords and identifiers
|
||||
# Keywords and identifiers
|
||||
self.parseIdentifier()
|
||||
elif self.match("#"):
|
||||
# Inline comments
|
||||
|
@ -567,7 +585,7 @@ proc next(self: Lexer) =
|
|||
discard self.step()
|
||||
self.createToken(Comment)
|
||||
else:
|
||||
# If none of the above conditiosn matched, there's a few
|
||||
# If none of the above conditions matched, there's a few
|
||||
# other options left:
|
||||
# - The token is a built-in operator, or
|
||||
# - it's an expression/statement delimiter, or
|
||||
|
@ -576,17 +594,19 @@ proc next(self: Lexer) =
|
|||
# match the longest sequence of characters possible
|
||||
# as either an operator or a statement/expression
|
||||
# delimiter, erroring out if there's no match
|
||||
var match = false
|
||||
var n = self.symbols.getMaxSymbolSize()
|
||||
while n > 0 and not match:
|
||||
while n > 0:
|
||||
for symbol in self.symbols.getSymbols(n):
|
||||
if self.match(symbol):
|
||||
match = true
|
||||
# We've found the largest possible
|
||||
# match!
|
||||
self.tokens.add(self.getToken(symbol))
|
||||
break
|
||||
return
|
||||
dec(n)
|
||||
if not match:
|
||||
self.error("invalid syntax")
|
||||
# None of our conditions matched: we don't know
|
||||
# what's sitting in front of us, but it definitely
|
||||
# isn't something we can parse, so it's an error
|
||||
self.error("invalid syntax")
|
||||
|
||||
|
||||
proc lex*(self: Lexer, source, file: string): seq[Token] =
|
||||
|
|
|
@ -63,7 +63,6 @@ type
|
|||
InplaceAnd, InplaceOr, FloorDiv, # &= |= //
|
||||
DoubleEqual, InplaceFloorDiv, InplacePow, # == //= **=
|
||||
InplaceRightShift, InplaceLeftShift, # >>= <<=
|
||||
Backtick, # `
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
|
@ -76,8 +75,8 @@ type
|
|||
Whitespace,
|
||||
Tab,
|
||||
|
||||
UnaryOperator, # Arbitrary user-defined unary operator
|
||||
BinaryOperator # Arbitrary user-defined binary operator
|
||||
CustomOperator, # Arbitrary user-defined operator (contains the operator's lexeme for operators
|
||||
# that are not identifiers)
|
||||
|
||||
|
||||
Token* = ref object
|
||||
|
|
159
src/test.nim
159
src/test.nim
|
@ -16,6 +16,7 @@ import jale/multiline
|
|||
import frontend/lexer as l
|
||||
import frontend/parser as p
|
||||
import frontend/compiler as c
|
||||
import backend/vm as v
|
||||
# TODO: Broken
|
||||
# import frontend/optimizer as o
|
||||
import util/serializer as s
|
||||
|
@ -35,7 +36,6 @@ const debugSerializer = true
|
|||
|
||||
|
||||
when isMainModule:
|
||||
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
var
|
||||
keep = true
|
||||
|
@ -50,6 +50,7 @@ when isMainModule:
|
|||
# optimizer = newOptimizer()
|
||||
compiler = newCompiler()
|
||||
serializer = newSerializer()
|
||||
vm = newPeonVM()
|
||||
editor = getLineEditor()
|
||||
input: string
|
||||
tokenizer.fillSymbolTable()
|
||||
|
@ -62,83 +63,86 @@ when isMainModule:
|
|||
while keep:
|
||||
try:
|
||||
input = editor.read()
|
||||
if input.len() > 0:
|
||||
# Currently the parser doesn't handle these tokens well
|
||||
tokens = filter(tokenizer.lex(input, "<stdin>"), proc (x: Token): bool = x.kind notin {TokenType.Whitespace, Tab})
|
||||
if tokens.len() > 0:
|
||||
when debugLexer:
|
||||
echo "Tokenization step:"
|
||||
for i, token in tokens:
|
||||
if i == tokens.high():
|
||||
# Who cares about EOF?
|
||||
break
|
||||
echo "\t", token
|
||||
echo ""
|
||||
tree = parser.parse(tokens, "<stdin>")
|
||||
when debugParser:
|
||||
echo "Parsing step:"
|
||||
for node in tree:
|
||||
echo "\t", node
|
||||
echo ""
|
||||
# The optimizer needs work to function properly
|
||||
# with the compiler
|
||||
# optimized = optimizer.optimize(tree)
|
||||
when debugOptimizer:
|
||||
echo &"Optimization step (constant folding enabled: {optimizer.foldConstants}):"
|
||||
for node in optimized.tree:
|
||||
echo "\t", node
|
||||
echo ""
|
||||
stdout.write(&"Produced warnings: ")
|
||||
if optimized.warnings.len() > 0:
|
||||
echo ""
|
||||
for warning in optimized.warnings:
|
||||
echo "\t", warning
|
||||
else:
|
||||
stdout.write("No warnings produced\n")
|
||||
echo ""
|
||||
compiled = compiler.compile(tree, "<stdin>")
|
||||
when debugCompiler:
|
||||
echo "Compilation step:"
|
||||
stdout.write("\t")
|
||||
echo &"""Raw byte stream: [{compiled.code.join(", ")}]"""
|
||||
echo "\nBytecode disassembler output below:\n"
|
||||
disassembleChunk(compiled, "<stdin>")
|
||||
if input.len() == 0:
|
||||
continue
|
||||
# Currently the parser doesn't handle these tokens well
|
||||
tokens = filter(tokenizer.lex(input, "<stdin>"), proc (x: Token): bool = x.kind notin {TokenType.Whitespace, Tab})
|
||||
if tokens.len() == 0:
|
||||
continue
|
||||
when debugLexer:
|
||||
echo "Tokenization step:"
|
||||
for i, token in tokens:
|
||||
if i == tokens.high():
|
||||
# Who cares about EOF?
|
||||
break
|
||||
echo "\t", token
|
||||
echo ""
|
||||
tree = parser.parse(tokens, "<stdin>")
|
||||
when debugParser:
|
||||
echo "Parsing step:"
|
||||
for node in tree:
|
||||
echo "\t", node
|
||||
echo ""
|
||||
# The optimizer needs work to function properly
|
||||
# with the compiler
|
||||
# optimized = optimizer.optimize(tree)
|
||||
when debugOptimizer:
|
||||
echo &"Optimization step (constant folding enabled: {optimizer.foldConstants}):"
|
||||
for node in optimized.tree:
|
||||
echo "\t", node
|
||||
echo ""
|
||||
stdout.write(&"Produced warnings: ")
|
||||
if optimized.warnings.len() > 0:
|
||||
echo ""
|
||||
for warning in optimized.warnings:
|
||||
echo "\t", warning
|
||||
else:
|
||||
stdout.write("No warnings produced\n")
|
||||
echo ""
|
||||
compiled = compiler.compile(tree, "<stdin>")
|
||||
when debugCompiler:
|
||||
echo "Compilation step:"
|
||||
stdout.write("\t")
|
||||
echo &"""Raw byte stream: [{compiled.code.join(", ")}]"""
|
||||
echo "\nBytecode disassembler output below:\n"
|
||||
disassembleChunk(compiled, "<stdin>")
|
||||
echo ""
|
||||
when debugSerializer:
|
||||
echo "Serialization step: "
|
||||
|
||||
echo "Dumping bytecode to 'stdin.pbc'\n"
|
||||
serializer.dumpToFile(compiled, input, "<stdin>", "stdin.pbc")
|
||||
serializedRaw = serializer.dumpBytes(compiled, input, "<stdin>")
|
||||
serialized = serializer.loadBytes(serializedRaw)
|
||||
when debugSerializer:
|
||||
echo "Serialization step: "
|
||||
|
||||
echo "Dumping bytecode to 'stdin.pbc'\n"
|
||||
serializer.dumpToFile(compiled, input, "<stdin>", "stdin.pbc")
|
||||
echo "Loading 'stdin.pbc'\n"
|
||||
serialized = serializer.loadFile("stdin.pbc")
|
||||
echo "Deserialized 'stdin.pbc':"
|
||||
stdout.write("\t")
|
||||
echo &"""Raw hex output: {serializedRaw.mapIt(toHex(it)).join("").toLowerAscii()}"""
|
||||
echo ""
|
||||
|
||||
echo "Loading 'stdin.pbc'\n"
|
||||
serialized = serializer.loadFile("stdin.pbc")
|
||||
echo "Deserialized 'stdin.pbc':"
|
||||
stdout.write("\t")
|
||||
echo &"""Raw hex output: {serializedRaw.mapIt(toHex(it)).join("").toLowerAscii()}"""
|
||||
echo ""
|
||||
|
||||
echo &"\t- File hash: {serialized.fileHash} (matches: {computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash})"
|
||||
echo &"\t- Peon version: {serialized.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch} (commit {serialized.commitHash[0..8]} on branch {serialized.peonBranch})"
|
||||
stdout.write("\t")
|
||||
echo &"""- Compilation date & time: {fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss")}"""
|
||||
stdout.write(&"\t- Reconstructed constants table: [")
|
||||
for i, e in serialized.chunk.consts:
|
||||
stdout.write(e)
|
||||
if i < len(serialized.chunk.consts) - 1:
|
||||
stdout.write(", ")
|
||||
stdout.write("]\n")
|
||||
stdout.write(&"\t- Reconstructed bytecode: [")
|
||||
for i, e in serialized.chunk.code:
|
||||
stdout.write($e)
|
||||
if i < len(serialized.chunk.code) - 1:
|
||||
stdout.write(", ")
|
||||
stdout.write(&"] (matches: {serialized.chunk.code == compiled.code})\n")
|
||||
echo &"\t- File hash: {serialized.fileHash} (matches: {computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash})"
|
||||
echo &"\t- Peon version: {serialized.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch} (commit {serialized.commitHash[0..8]} on branch {serialized.peonBranch})"
|
||||
stdout.write("\t")
|
||||
echo &"""- Compilation date & time: {fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss")}"""
|
||||
stdout.write(&"\t- Reconstructed constants table: [")
|
||||
for i, e in serialized.chunk.consts:
|
||||
stdout.write(e)
|
||||
if i < len(serialized.chunk.consts) - 1:
|
||||
stdout.write(", ")
|
||||
stdout.write("]\n")
|
||||
stdout.write(&"\t- Reconstructed bytecode: [")
|
||||
for i, e in serialized.chunk.code:
|
||||
stdout.write($e)
|
||||
if i < len(serialized.chunk.code) - 1:
|
||||
stdout.write(", ")
|
||||
stdout.write(&"] (matches: {serialized.chunk.code == compiled.code})\n")
|
||||
echo "Execution step: "
|
||||
vm.run(serialized.chunk)
|
||||
except IOError:
|
||||
break
|
||||
# TODO: The code for error reporting completely
|
||||
# breaks down with multiline input, fix it
|
||||
# TODO: The code for error reporting completely
|
||||
# breaks down with multiline input, fix it
|
||||
except LexingError:
|
||||
let lineNo = tokenizer.getLine()
|
||||
let relPos = tokenizer.getRelPos(lineNo)
|
||||
|
@ -172,7 +176,6 @@ proc fillSymbolTable(tokenizer: Lexer) =
|
|||
## and keywords
|
||||
|
||||
# 1-byte symbols
|
||||
tokenizer.symbols.addSymbol("`", Backtick)
|
||||
tokenizer.symbols.addSymbol("+", Plus)
|
||||
tokenizer.symbols.addSymbol("-", Minus)
|
||||
tokenizer.symbols.addSymbol("*", Star)
|
||||
|
@ -245,11 +248,11 @@ proc fillSymbolTable(tokenizer: Lexer) =
|
|||
tokenizer.symbols.addKeyword("import", Import)
|
||||
tokenizer.symbols.addKeyword("yield", TokenType.Yield)
|
||||
tokenizer.symbols.addKeyword("return", TokenType.Return)
|
||||
# These are technically more like expressions
|
||||
# with a reserved name that produce a value of a
|
||||
# builtin type, but we don't need to care about
|
||||
# that until we're in the parsing and compilation
|
||||
# steps so it's fine
|
||||
# These are more like expressions with a reserved
|
||||
# name that produce a value of a builtin type,
|
||||
# but we don't need to care about that until
|
||||
# we're in the parsing/ compilation steps so
|
||||
# it's fine
|
||||
tokenizer.symbols.addKeyword("nan", NotANumber)
|
||||
tokenizer.symbols.addKeyword("inf", Infinity)
|
||||
tokenizer.symbols.addKeyword("nil", TokenType.Nil)
|
||||
|
|
Loading…
Reference in New Issue