Removed old JAPL type system. Initial dummy VM for testing purposes

This commit is contained in:
Mattia Giambirtone 2022-04-28 18:06:53 +02:00
parent 0e4eb82554
commit 223ba603a7
14 changed files with 220 additions and 776 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &= "}"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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] =

View File

@ -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

View File

@ -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)