commit a80dd4bff3b2be3d8605d8318b427a02f5ac1f0d Author: Calvin Rose Date: Thu Feb 9 15:02:59 2017 -0500 First commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..dc87d95c --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ + +# Created by https://www.gitignore.io/api/c + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# End of https://www.gitignore.io/api/c \ No newline at end of file diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 00000000..0f14db77 --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,141 @@ +# Generated by YCM Generator at 2016-05-24 22:59:35.570087 + +# This file is NOT licensed under the GPLv3, which is the license for the rest +# of YouCompleteMe. +# +# Here's the license text for this file: +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +import os +import ycm_core + +flags = [ + '-x', + 'c', + '-I/home/calvin/code/bkdoc/include', + '-I/home/calvin/code/bkdoc/src', + '-I/home/calvin/code/bkdoc/cli', + '-std=gnu99', +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# You can get CMake to generate this file for you by adding: +# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) +# to your CMakeLists.txt file. +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '' + +if os.path.exists( compilation_database_folder ): + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c9a9e2f4 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +# TIL + +CFLAGS=-std=c99 -Wall -Wextra -m32 -g + +TARGET=interp +PREFIX=/usr/local + +# C sources +SOURCES=main.c parse.c value.c vm.c dict.c array.c buffer.c gc.c compile.c +OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(CFLAGS) -o $(TARGET) $(OBJECTS) + +%.o : %.c + $(CC) $(CFLAGS) -o $@ -c $< + +install: $(TARGET) + cp $(TARGET) $(PREFIX)/bin + +clean: + rm $(TARGET) || true + rm $(OBJECTS) || true + +run: $(TARGET) + ./$(TARGET) + +debug: $(TARGET) + gdb $(TARGET) + +.PHONY: clean install run debug diff --git a/README.md b/README.md new file mode 100644 index 00000000..eca7a8d2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tape + +A language and vm for embeddable scripting. diff --git a/array.c b/array.c new file mode 100644 index 00000000..e22c0954 --- /dev/null +++ b/array.c @@ -0,0 +1,78 @@ +#include +#include "array.h" +#include "gc.h" + +/* Creates a new array */ +Array * ArrayNew(GC * gc, uint32_t capacity) { + Array * array = GCAlloc(gc, sizeof(Array)); + Value * data = GCAlloc(gc, capacity * sizeof(Value)); + array->data = data; + array->count = 0; + array->capacity = capacity; + return array; +} + +/* Ensure the array has enough capacity for capacity elements */ +void ArrayEnsure(GC * gc, Array * array, uint32_t capacity) { + Value * newData; + if (capacity <= array->capacity) return; + newData = GCAlloc(gc, capacity * sizeof(Value)); + memcpy(newData, array->data, array->count * sizeof(Value)); + array->data = newData; + array->capacity = capacity; +} + +/* Get a value of an array with bounds checking. */ +Value ArrayGet(Array * array, uint32_t index) { + if (index < array->count) { + return array->data[index]; + } else { + Value v; + v.type = TYPE_NIL; + v.data.boolean = 0; + return v; + } +} + +/* Try to set an index in the array. Return 1 if successful, 0 + * on failiure */ +int ArraySet(Array * array, uint32_t index, Value x) { + if (index < array->count) { + array->data[index] = x; + return 1; + } else { + return 0; + } +} + +/* Add an item to the end of the array */ +void ArrayPush(GC * gc, Array * array, Value x) { + if (array->count >= array->capacity) { + ArrayEnsure(gc, array, 2 * array->count); + } + array->data[array->count++] = x; +} + +/* Remove the last item from the Array and return it */ +Value ArrayPop(Array * array) { + if (array->count) { + return array->data[--array->count]; + } else { + Value v; + v.type = TYPE_NIL; + v.data.boolean = 0; + return v; + } +} + +/* Look at the last item in the Array */ +Value ArrayPeek(Array * array) { + if (array->count) { + return array->data[array->count - 1]; + } else { + Value v; + v.type = TYPE_NIL; + v.data.boolean = 0; + return v; + } +} diff --git a/array.h b/array.h new file mode 100644 index 00000000..20573793 --- /dev/null +++ b/array.h @@ -0,0 +1,30 @@ +#ifndef ARRAY_H_BW48JZSK +#define ARRAY_H_BW48JZSK + +#include "datatypes.h" + +/* Create a new Array */ +Array * ArrayNew(GC * gc, uint32_t capacity); + +/* Get a value of an array with bounds checking. Returns nil if + * outside bounds. */ +Value ArrayGet(Array * array, uint32_t index); + +/* Set a value in the array. Does bounds checking but will not grow + * or shrink the array */ +int ArraySet(Array * array, uint32_t index, Value x); + +/* Ensure that the internal memory hash enough space for capacity items */ +void ArrayEnsure(GC * gc, Array * array, uint32_t capacity); + +/* Set a value in an array. Will also append to the array if the index is + * greater than the current max index. */ +void ArrayPush(GC * gc, Array * array, Value x); + +/* Pop the last item in the array, or return NIL if empty */ +Value ArrayPop(Array * array); + +/* Look at the top most item of an Array */ +Value ArrayPeek(Array * array); + +#endif /* end of include guard: ARRAY_H_BW48JZSK */ diff --git a/buffer.c b/buffer.c new file mode 100644 index 00000000..5288e84a --- /dev/null +++ b/buffer.c @@ -0,0 +1,62 @@ +#include +#include "buffer.h" +#include "gc.h" +#include "value.h" +#include "vstring.h" + +void BufferInit(GC * gc, Buffer * buffer, uint32_t capacity) { + uint8_t * data; + data = GCAlloc(gc, sizeof(uint8_t) * capacity); + buffer->data = data; + buffer->count = 0; + buffer->capacity = capacity; +} + +Buffer * BufferNew(GC * gc, uint32_t capacity) { + Buffer * buffer; + buffer = GCAlloc(gc, sizeof(Buffer)); + BufferInit(gc, buffer, capacity); + return buffer; +} + +void BufferEnsure(GC * gc, Buffer * buffer, uint32_t capacity) { + uint8_t * newData; + if (capacity <= buffer->capacity) return; + newData = GCAlloc(gc, capacity * sizeof(uint8_t)); + memcpy(newData, buffer->data, buffer->count * sizeof(uint8_t)); + buffer->data = newData; + buffer->capacity = capacity; +} + +int32_t BufferGet(Buffer * buffer, uint32_t index) { + if (index < buffer->count) { + return buffer->data[index]; + } else { + return -1; + } +} + +void BufferPush(GC * gc, Buffer * buffer, uint8_t c) { + if (buffer->count >= buffer->capacity) { + BufferEnsure(gc, buffer, 2 * buffer->count); + } + buffer->data[buffer->count++] = c; +} + +void BufferAppendData(GC * gc, Buffer * buffer, uint8_t * string, uint32_t length) { + uint32_t newSize = buffer->count + length; + if (newSize > buffer->capacity) { + BufferEnsure(gc, buffer, 2 * newSize); + } + memcpy(buffer->data + buffer->count, string, length); + buffer->count = newSize; +} + +uint8_t * BufferToString(GC * gc, Buffer * buffer) { + uint8_t * data = GCAlloc(gc, buffer->count + 2 * sizeof(uint32_t)); + data += 2 * sizeof(uint32_t); + VStringSize(data) = buffer->count; + VStringHash(data) = 0; + memcpy(data, buffer->data, buffer->count * sizeof(uint8_t)); + return data; +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 00000000..cc05174f --- /dev/null +++ b/buffer.h @@ -0,0 +1,26 @@ +#ifndef BUFFER_H_OEA9T4DJ +#define BUFFER_H_OEA9T4DJ + +#include "datatypes.h" + +void BufferInit(GC * gc, Buffer * buffer, uint32_t capacity); + +Buffer * BufferNew(GC * gc, uint32_t capacity); + +void BufferEnsure(GC * gc, Buffer * buffer, uint32_t capacity); + +int32_t BufferGet(Buffer * buffer, uint32_t index); + +void BufferPush(GC * gc, Buffer * buffer, uint8_t c); + +void BufferAppendData(GC * gc, Buffer * buffer, uint8_t * string, uint32_t length); + +uint8_t * BufferToString(GC * gc, Buffer * buffer); + +#define BufferDefine(name, type) \ +static void BufferPush##name (GC * gc, Buffer * buffer, type x) { \ + union { type t; uint8_t bytes[sizeof(type)]; } u; \ + u.t = x; return BufferAppendData(gc, buffer, u.bytes, sizeof(type)); \ +} + +#endif /* end of include guard: BUFFER_H_OEA9T4DJ */ diff --git a/compile.c b/compile.c new file mode 100644 index 00000000..4e32be4f --- /dev/null +++ b/compile.c @@ -0,0 +1,1163 @@ +#include "compile.h" +#include "buffer.h" +#include "array.h" +#include "dict.h" +#include "gc.h" +#include "opcodes.h" +#include "vstring.h" +#include "value.h" +#include "vm.h" +#include + +/* During compilation, FormOptions are passed to ASTs + * as configuration options to allow for some optimizations. */ +typedef struct FormOptions FormOptions; +struct FormOptions { + /* The location the returned Slot must be in. Can be ignored + * if either canDrop or canChoose is true */ + uint16_t target; + /* Drop expression result flag - Allows for low level special + * forms that generate reasonably efficient byteCode. When a compiler + * helper is passed this flag, this allows the helper to return a dud + * slot. */ + uint16_t canDrop : 1; + /* Allows the sub expression to evaluate into a + * temporary slot of it's choice. A temporary Slot + * can be allocated with CompilerGetLocal. */ + uint16_t canChoose : 1; + /* True if the form is in the tail position. This allows + * for tail call optimization. If a helper receives this + * flag, it is free to return a dud slot and generate bytecode + * for a return, including tail calls. If a dud slot is returned, the + * implementation should generate the instructions for return. If the slot + * is not a dud, an upper function is in charge of generating the return. */ + uint16_t isTail : 1; +}; + +/* A Slot represent a location of a local variable + * on the stack. Also contains some meta information. */ +typedef struct Slot Slot; +struct Slot { + /* The index of the Slot on the stack. */ + uint16_t index; + /* A dud Slot should be expected to contain real data. + * Forms that have side effects but don't evaulate to + * anything will try to return dud slots if they can. If + * they can't (FormOptions.canDrop is false), the will + * usually load a Nil Slot and return that. That incurs + * some overhead that is undesirable. */ + uint16_t isDud : 1; + /* A temp Slot is a Slot on the stack that does not + * belong to a named local. They can be freed whenever, + * and so are used in intermediate calculations. */ + uint16_t isTemp : 1; +}; + +/* A SlotTracker provides a handy way to keep track of + * Slots on the stack and free them in bulk. */ +typedef struct SlotTracker SlotTracker; +struct SlotTracker { + Slot * slots; + uint32_t count; + uint32_t capacity; +}; + +/* A Scope is a lexical scope in the program. It is + * responsible for aliasing programmer facing names to + * Slots and for keeping track of literals. It also + * points to the parent Scope, and its current child + * Scope. */ +struct Scope { + uint16_t nextLocal; + uint32_t heapCapacity; + uint32_t heapSize; + uint16_t * freeHeap; + Dictionary * literals; + Array * literalsArray; + Dictionary * locals; + Scope * nextScope; + Scope * previousScope; +}; + +/* Provides a default Slot. */ +static Slot SlotDefault() { + Slot slot; + slot.index = 0; + slot.isDud = 0; + slot.isTemp = 0; + return slot; +} + +/* Provides default FormOptions */ +static FormOptions FormOptionsDefault() { + FormOptions opts; + opts.canChoose = 1; + opts.isTail = 0; + opts.canDrop = 0; + opts.target = 0; + return opts; +} + +/* Create some helpers that allows us to push more than just raw bytes + * to the byte buffer. This helps us create the byte code for the compiled + * functions. */ +BufferDefine(UInt32, uint32_t); +BufferDefine(Int32, int32_t); +BufferDefine(Number, Number); +BufferDefine(UInt16, uint16_t); +BufferDefine(Int16, int16_t); + +/* If there is an error during compilation, + * jump back to start */ +#define CError(c, e) ((c)->error = (e), longjmp((c)->onError, 1)) + +/* Get the garbage collector for the compiler */ +#define CompilerGC(c) (&(c)->vm->gc) + +/* Push a new scope in the compiler and return + * a pointer to it for configuration. There is + * more configuration that needs to be done if + * the new scope is a function declaration. */ +static Scope * CompilerPushScope(Compiler * c, int sameFunction) { + GC * gc = CompilerGC(c); + Scope * scope = GCAlloc(gc, sizeof(Scope)); + scope->locals = DictNew(gc, 10); + scope->freeHeap = GCAlloc(gc, 10 * sizeof(uint16_t)); + scope->heapSize = 0; + scope->heapCapacity = 10; + scope->nextScope = NULL; + scope->previousScope = c->tail; + if (c->tail) + c->tail->nextScope = scope; + if (sameFunction) { + if (!c->tail) { + CError(c, "Cannot inherit scope when root scope"); + } + scope->nextLocal = c->tail->nextLocal; + scope->literals = c->tail->literals; + scope->literalsArray = c->tail->literalsArray; + } else { + scope->nextLocal = 0; + scope->literals = DictNew(gc, 10); + scope->literalsArray = ArrayNew(gc, 10); + } + c->tail = scope; + if (!c->root) + c->root = scope; + return scope; +} + +/* Remove the inner most scope from the compiler stack */ +static void CompilerPopScope(Compiler * c) { + if (c->tail == NULL) { + CError(c, "No scope to pop."); + } else { + c->tail = c->tail->previousScope; + if (c->tail) { + c->tail->nextScope = NULL; + } else { + /* We deleted the last scope */ + c->root = NULL; + } + } +} + +/* Get the next stack position that is open for + * a variable */ +static uint16_t CompilerGetLocal(Compiler * c, Scope * scope) { + if (scope->heapSize == 0) { + if (scope->nextLocal + 1 == 0) { + CError(c, "Too many local variables. Try splitting up your functions :)"); + } + return scope->nextLocal++; + } else { + uint16_t ret = scope->freeHeap[0]; + uint16_t left, right, current, * heap; + uint32_t currentIndex = 0; + heap = scope->freeHeap; + heap[0] = heap[--scope->heapSize]; + /* Min Heap Bubble down */ + while (currentIndex < scope->heapSize) { + uint32_t leftIndex = 2 * currentIndex + 1; + uint32_t rightIndex = leftIndex + 1; + current = heap[currentIndex]; + left = heap[leftIndex]; + right = heap[rightIndex]; + if (current == left || current == right) { + CError(c, "Double slot allocation. Error in Compiler."); + } + if (left < current && left <= right) { + heap[currentIndex] = left; + heap[leftIndex] = current; + currentIndex = leftIndex; + } else if (right < current && right <= left) { + heap[currentIndex] = left; + heap[leftIndex] = current; + currentIndex = leftIndex; + } else { + break; + } + } + return ret; + } + return 0; +} + +/* Free a slot on the stack for other locals and/or + * intermediate values */ +static void CompilerFreeLocal(Compiler * c, Scope * scope, uint16_t slot) { + GC * gc = CompilerGC(c); + if (slot == scope->nextLocal - 1) { + --scope->nextLocal; + return; + } else { + uint32_t current; + uint32_t parent; + uint16_t * heap; + /* Ensure heap has space */ + if (scope->heapSize >= scope->heapCapacity) { + uint32_t newCap = 2 * scope->heapSize; + uint16_t * newData = GCAlloc(gc, newCap * sizeof(uint16_t)); + memcpy(newData, scope->freeHeap, scope->heapSize * sizeof(uint16_t)); + scope->freeHeap = newData; + scope->heapCapacity = newCap; + } + heap = scope->freeHeap; + current = scope->heapSize++; + /* Min heap bubble up */ + while (current > 0) { + parent = (current - 1) / 2; + if (slot == heap[parent]) { + CError(c, "Double local free. Error in compiler."); + } + if (slot < heap[parent]) { + heap[current] = slot; + return; + } + heap[current] = heap[parent]; + current = parent; + } + heap[0] = slot; + } +} + +/* Initializes a SlotTracker. SlotTrackers + * are used during compilation to free up slots on the stack + * after they are no longer needed. */ +static void CompilerTrackerInit(Compiler * c, SlotTracker * tracker) { + GC * gc = CompilerGC(c); + tracker->slots = GCAlloc(gc, 10 * sizeof(Slot)); + tracker->count = 0; + tracker->capacity = 10; +} + +/* Free up a slot if it is a temporary slot (does not + * belong to a named local). If the slot does belong + * to a named variable, does nothing. */ +static void CompilerDropSlot(Compiler * c, Scope * scope, Slot slot) { + if (slot.isTemp && !(slot.isTemp)) { + CompilerFreeLocal(c, scope, slot.index); + } +} + +/* Free the tracker after creation. This unlocks the memory + * that was allocated by the GC an allows it to be collected. Also + * frees slots that were tracked by this tracker in the given scope. + * Also optionally write slot locations of all slots to the buffer. + * Useful for dictionary literals, array literals, function calls, etc. */ +static void CompilerTrackerFree(Compiler * c, Scope * scope, SlotTracker * tracker, int writeToBuffer) { + GC * gc = CompilerGC(c); + uint32_t i; + if (writeToBuffer) { + Buffer * buffer = c->buffer; + for (i = 0; i < tracker->count; ++i) { + Slot * s = tracker->slots + i; + BufferPushUInt16(gc, buffer, s->index); + } + } + /* Free in reverse order */ + for (i = tracker->count - 1; i < tracker->count; --i) { + CompilerDropSlot(c, scope, tracker->slots[i]); + } + tracker->slots = NULL; +} + +/* Add a new Slot to a slot tracker. */ +static void CompilerTrackerPush(Compiler * c, SlotTracker * tracker, Slot slot) { + GC * gc = CompilerGC(c); + if (tracker->count >= tracker->capacity) { + uint32_t newCap = 2 * tracker->count; + Slot * newData = GCAlloc(gc, newCap * sizeof(Slot)); + memcpy(newData, tracker->slots, tracker->count * sizeof(Slot)); + tracker->slots = newData; + tracker->capacity = newCap; + } + tracker->slots[tracker->count++] = slot; +} + +/* Registers a literal in the given scope. If an equal literal is found, uses + * that one instead of creating a new literal. This allows for some reuse + * of things like string constants.*/ +static uint16_t CompilerAddLiteral(Compiler * c, Scope * scope, Value x) { + GC * gc = CompilerGC(c); + Value checkDup = DictGet(scope->literals, &x); + uint16_t literalIndex = 0; + if (checkDup.type != TYPE_NIL) { + /* An equal literal is already registered in the current scope */ + return (uint16_t) checkDup.data.number; + } else { + /* Add our literal for tracking */ + Value valIndex; + valIndex.type = TYPE_NUMBER; + literalIndex = scope->literalsArray->count; + DictPut(gc, scope->literals, &valIndex, &x); + ArrayPush(gc, scope->literalsArray, x); + } + return literalIndex; +} + +/* Declare a symbol in a given scope. */ +static uint16_t CompilerDeclareSymbol(Compiler * c, Scope * scope, Value sym) { + GC * gc = CompilerGC(c); + if (sym.type != TYPE_SYMBOL) { + CError(c, "Expected symbol"); + } + Value x; + uint16_t target = CompilerGetLocal(c, scope); + x.type = TYPE_NUMBER; + x.data.number = target; + DictPut(gc, scope->locals, &sym, &x); + return target; +} + +/* Try to resolve a symbol. If the symbol can be resovled, return true and + * pass back the level and index by reference. */ +static int ScopeSymbolResolve(Scope * scope, Value x, + uint16_t * level, uint16_t * index) { + uint16_t levelTest = 0; + while (scope) { + Value check = DictGet(scope->locals, &x); + if (check.type != TYPE_NIL) { + *level = levelTest; + *index = (uint16_t) check.data.number; + return 1; + } + ++levelTest; + scope = scope->previousScope; + } + return 0; +} + +/* Forward declaration */ +/* Compile a value and return it stack location after loading. + * If a target > 0 is passed, the returned value must be equal + * to the targtet. If target < 0, the Compiler can choose whatever + * slot location it likes. If, for example, a symbol resolves to + * whatever is in a given slot, it makes sense to use that location + * to 'return' the value. For other expressions, like function + * calls, the compiler will just pick the lowest free slot + * as the location on the stack. */ +static Slot CompileValue(Compiler * c, FormOptions opts, Value x); + +/* Compile a structure that evaluates to a literal value. Useful + * for objects like strings, or anything else that cannot be instatiated + * from bytecode and doesn't do anything in the AST. */ +static Slot CompileLiteral(Compiler * c, FormOptions opts, Value x) { + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + GC * gc = CompilerGC(c); + Slot ret = SlotDefault(); + uint16_t literalIndex; + if (opts.canDrop) { + ret.isDud = 1; + return ret; + } + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + literalIndex = CompilerAddLiteral(c, scope, x); + BufferPushUInt16(gc, buffer, VM_OP_CST); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, literalIndex); + return ret; +} + +/* Compile boolean, nil, and number values. */ +static Slot CompileNonReferenceType(Compiler * c, FormOptions opts, Value x) { + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + GC * gc = CompilerGC(c); + Slot ret = SlotDefault(); + /* If the value is not used, the compiler can just immediately + * ignore it as there are no side effects. */ + if (opts.canDrop) { + ret.isDud = 1; + return ret; + } + if (opts.canChoose) { + ret.index = CompilerGetLocal(c, scope); + ret.isTemp = 1; + } else { + ret.index = (uint16_t) opts.target; + } + if (x.type == TYPE_NIL) { + BufferPushUInt16(gc, buffer, VM_OP_NIL); + BufferPushUInt16(gc, buffer, ret.index); + } else if (x.type == TYPE_BOOLEAN) { + BufferPushUInt16(gc, buffer, x.data.boolean ? VM_OP_TRU : VM_OP_FLS); + BufferPushUInt16(gc, buffer, ret.index); + } else if (x.type == TYPE_NUMBER) { + Number number = x.data.number; + int32_t int32Num = (int32_t) number; + if (number == (Number) int32Num) { + if (int32Num <= 32767 && int32Num >= -32768) { + int16_t int16Num = (int16_t) number; + BufferPushUInt16(gc, buffer, VM_OP_I16); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushInt16(gc, buffer, int16Num); + } else { + BufferPushUInt16(gc, buffer, VM_OP_I32); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushInt32(gc, buffer, int32Num); + } + } else { + BufferPushUInt16(gc, buffer, VM_OP_F64); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushNumber(gc, buffer, number); + } + } else { + CError(c, "Expected boolean, nil, or number type."); + } + return ret; +} + +/* Compile a symbol. Resolves any kind of symbol. */ +static Slot CompileSymbol(Compiler * c, FormOptions opts, Value sym) { + Buffer * buffer = c->buffer; + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Slot ret = SlotDefault(); + uint16_t index = 0; + uint16_t level = 0; + /* We can just do nothing if we are dropping the + * results, as dereferencing a symbol has no side effects. */ + if (opts.canDrop) { + ret.isDud = 1; + return ret; + } + if (ScopeSymbolResolve(scope, sym, &level, &index)) { + if (level > 0) { + /* We have an upvalue */ + if (opts.canChoose) { + ret.index = CompilerGetLocal(c, scope); + ret.isTemp = 1; + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, VM_OP_UPV); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, level); + BufferPushUInt16(gc, buffer, index); + } else { + /* Local variable on stack */ + if (opts.canChoose) { + ret.index = index; + } else { + /* We need to move the variable. This + * would occur in a simple assignment like a = b. */ + ret.index = opts.target; + BufferPushUInt16(gc, buffer, VM_OP_MOV); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, index); + } + } + } else { + CError(c, "Undefined symbol"); + } + return ret; +} + +/* Compile a dictionary literal. The order of compilation + * is undefined, although a key is evalated before its value, + * assuming the dictionary is unchanged by macros. */ +static Slot CompileDict(Compiler * c, FormOptions opts, Dictionary * dict) { + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + GC * gc = CompilerGC(c); + Slot ret = SlotDefault(); + FormOptions subOpts = FormOptionsDefault(); + DictionaryIterator iter; + DictBucket * bucket; + SlotTracker tracker; + /* Calculate sub flags */ + subOpts.canDrop = opts.canDrop; + /* Compile all of the arguments */ + CompilerTrackerInit(c, &tracker); + DictIterate(dict, &iter); + while (DictIterateNext(&iter, &bucket)) { + Slot keySlot = CompileValue(c, subOpts, bucket->key); + if (subOpts.canDrop) CompilerDropSlot(c, scope, keySlot); + Slot valueSlot = CompileValue(c, subOpts, bucket->value); + if (subOpts.canDrop) CompilerDropSlot(c, scope, valueSlot); + if (!subOpts.canDrop) { + CompilerTrackerPush(c, &tracker, keySlot); + CompilerTrackerPush(c, &tracker, valueSlot); + } + } + if (!opts.canDrop) { + /* Write Dictionary literal opcode */ + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, VM_OP_DIC); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, dict->count); + } else { + ret.isDud = 1; + } + /* Write the location of all of the arguments */ + CompilerTrackerFree(c, scope, &tracker, 1); + return ret; +} + +/* Compile an array literal. The array is evaluated left + * to right. Arrays are normally compiled as forms to be evaluated, however. */ +static Slot CompileArray(Compiler * c, FormOptions opts, Array * array) { + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + GC * gc = CompilerGC(c); + Slot ret = SlotDefault(); + FormOptions subOpts = FormOptionsDefault(); + SlotTracker tracker; + uint32_t i; + /* Calculate sub flags */ + subOpts.canDrop = opts.canDrop; + /* Compile all of the arguments */ + CompilerTrackerInit(c, &tracker); + for (i = 0; i < array->count; ++i) { + Slot slot = CompileValue(c, subOpts, array->data[i]); + if (subOpts.canDrop) + CompilerDropSlot(c, scope, slot); + else + CompilerTrackerPush(c, &tracker, slot); + } + if (!opts.canDrop) { + /* Write Array literal opcode */ + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, VM_OP_ARR); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, array->count); + } else { + ret.isDud = 1; + } + /* Write the location of all of the arguments */ + CompilerTrackerFree(c, scope, &tracker, 1); + return ret; +} + +/* Compile a special form in the form of an operator. There + * are four choices for opcodes - when the operator is called + * with 0, 1, 2, or n arguments. When the operator form is + * called with n arguments, the number of arguments is written + * after the op code, followed by those arguments. + * + * This makes a few assumptions about the opertors. One, no side + * effects. With this assumptions, if the result of the operator + * is unused, it's calculation can be ignored (the evaluation of + * its argument is still carried out, but their results can + * also be ignored). */ +static Slot CompileOperator(Compiler * c, FormOptions opts, Array * form, + int16_t op0, int16_t op1, int16_t op2, int16_t opn) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + Slot ret = SlotDefault(); + FormOptions subOpts = FormOptionsDefault(); + SlotTracker tracker; + uint32_t i; + /* Calculate sub options */ + subOpts.canDrop = opts.canDrop; + /* Compile all of the arguments */ + CompilerTrackerInit(c, &tracker); + for (i = 1; i < form->count; ++i) { + Slot slot = CompileValue(c, subOpts, form->data[i]); + if (subOpts.canDrop) + CompilerDropSlot(c, scope, slot); + else + CompilerTrackerPush(c, &tracker, slot); + } + if (!opts.canDrop) { + /* Write the correct opcode */ + if (form->count < 2) { + if (op0 < 0) CError(c, "This operator does not take 0 arguments."); + BufferPushUInt16(gc, buffer, op0); + } else if (form->count == 2) { + if (op1 < 0) CError(c, "This operator does not take 1 argument."); + BufferPushUInt16(gc, buffer, op1); + } else if (form->count == 3) { + if (op2 < 0) CError(c, "This operator does not take 2 arguments."); + BufferPushUInt16(gc, buffer, op2); + } else { + if (opn < 0) CError(c, "This operator does not take n arguments."); + BufferPushUInt16(gc, buffer, opn); + BufferPushUInt16(gc, buffer, form->count - 1); + } + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.isDud = opts.target; + } + BufferPushUInt16(gc, buffer, ret.index); + } + /* Write the location of all of the arguments */ + CompilerTrackerFree(c, scope, &tracker, 1); + return ret; +} + +/* Math specials */ +static Slot CompileAddition(Compiler * c, FormOptions opts, Array * form) { + return CompileOperator(c, opts, form, VM_OP_LD0, VM_OP_ADM, VM_OP_ADD, VM_OP_ADM); +} +static Slot CompileSubtraction(Compiler * c, FormOptions opts, Array * form) { + return CompileOperator(c, opts, form, VM_OP_LD0, VM_OP_SBM, VM_OP_SUB, VM_OP_SBM); +} +static Slot CompileMultiplication(Compiler * c, FormOptions opts, Array * form) { + return CompileOperator(c, opts, form, VM_OP_LD1, VM_OP_MUM, VM_OP_MUL, VM_OP_MUM); +} +static Slot CompileDivision(Compiler * c, FormOptions opts, Array * form) { + return CompileOperator(c, opts, form, VM_OP_LD1, VM_OP_DVM, VM_OP_DIV, VM_OP_DVM); +} + +/* Compile an assignment operation */ +static Slot CompileAssign(Compiler * c, FormOptions opts, Value left, Value right) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + FormOptions subOpts = FormOptionsDefault(); + Slot ret = SlotDefault(); + uint16_t target = 0; + uint16_t level = 0; + if (ScopeSymbolResolve(scope, left, &level, &target)) { + /* Check if we have an up value. Otherwise, it's just a normal + * local variable */ + if (level != 0) { + /* Evaluate the right hand side */ + Slot slot = CompileValue(c, subOpts, right); + /* Set the up value */ + BufferPushUInt16(gc, buffer, VM_OP_SUV); + BufferPushUInt16(gc, buffer, slot.index); + BufferPushUInt16(gc, buffer, level); + BufferPushUInt16(gc, buffer, target); + /* Drop the possibly temporary slot if it is indeed temporary */ + CompilerDropSlot(c, scope, slot); + return ret; + } + } else { + /* We need to declare a new symbol */ + subOpts.target = CompilerDeclareSymbol(c, scope, left); + subOpts.canChoose = 0; + CompileValue(c, subOpts, right); + } + /* If we need a return value, we just use nil */ + if (!opts.canDrop) { + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, VM_OP_NIL); + BufferPushUInt16(gc, buffer, ret.index); + } + return ret; +} + +/* Writes bytecode to return a slot */ +static void CompilerReturnSlot(Compiler * c, Slot slot) { + GC * gc = CompilerGC(c); + Buffer * buffer = c->buffer; + if (slot.isDud) { + BufferPushUInt16(gc, buffer, VM_OP_RTN); + } else { + BufferPushUInt16(gc, buffer, VM_OP_RET); + BufferPushUInt16(gc, buffer, slot.index); + } +} + +/* Compile series of expressions. This compiles the meat of + * function definitions and the inside of do forms. */ +static Slot CompileBlock(Compiler * c, FormOptions opts, Array * form, uint32_t startIndex) { + Scope * scope = c->tail; + Slot ret = SlotDefault(); + FormOptions subOpts = FormOptionsDefault(); + uint32_t current = startIndex; + /* Compile the body */ + while (current < form->count) { + subOpts.canDrop = current != form->count - 1; + subOpts.isTail = opts.isTail && !subOpts.canDrop; + ret = CompileValue(c, subOpts, form->data[current]); + if (subOpts.canDrop) { + CompilerDropSlot(c, scope, ret); + ret.isDud = 1; + } + ++current; + } + if (opts.isTail && !ret.isDud) { + CompilerReturnSlot(c, ret); + ret.isDud = 1; + } + return ret; +} + +/* Extract the last n bytes from the buffer and use them to construct + * a function definition. */ +static FuncDef * CompilerGenFuncDef(Compiler * c, uint32_t lastNBytes, uint32_t arity) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + FuncDef * def = GCAlloc(gc, sizeof(FuncDef)); + /* Create enough space for the new byteCode */ + if (lastNBytes > buffer->count) { + CError(c, "Trying to extract more bytes from buffer than in buffer."); + } + uint8_t * byteCode = GCAlloc(gc, lastNBytes); + def->byteCode = (uint16_t *) byteCode; + def->byteCodeLen = lastNBytes / 2; + /* Copy the last chunk of bytes in the buffer into the new + * memory for the function's byteCOde */ + memcpy(byteCode, buffer->data + buffer->count - lastNBytes, lastNBytes); + /* Remove the byteCode from the end of the buffer */ + buffer->count -= lastNBytes; + /* Initialize the new FuncDef */ + def->locals = scope->nextLocal; + def->arity = arity; + /* Create the literals used by this function */ + if (scope->literalsArray->count) { + def->literals = GCAlloc(gc, scope->literalsArray->count * sizeof(Value)); + memcpy(def->literals, scope->literalsArray->data, + scope->literalsArray->count * sizeof(Value)); + } else { + def->literals = NULL; + } + def->literalsLen = scope->literalsArray->count; + /* Delete the sub scope */ + CompilerPopScope(c); + return def; +} + +/* Compile a function from a function literal */ +static Slot CompileFunction(Compiler * c, FormOptions opts, Array * form) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + uint32_t current = 1; + uint32_t i; + uint32_t sizeBefore; /* Size of buffer before compiling function */ + Scope * subScope; + Array * params; + FormOptions subOpts = FormOptionsDefault(); + Slot ret = SlotDefault(); + /* Do nothing if we can drop. This is rather pointless from + * a language point of view, but it doesn't hurt. */ + if (opts.canDrop) { + ret.isDud = 1; + return ret; + } + subScope = CompilerPushScope(c, 0); + /* Check for function documentation - for now just ignore. */ + if (form->data[current].type == TYPE_STRING) { + ++current; + } + /* Define the function parameters */ + if (form->data[current].type != TYPE_ARRAY) { + CError(c, "Expected function arguments"); + } + params = form->data[current++].data.array; + for (i = 0; i < params->count; ++i) { + Value param = params->data[i]; + if (param.type != TYPE_SYMBOL) { + CError(c, "Function parameters should be symbols"); + } + /* The compiler puts the parameter locals + * in the right place by default - at the beginning + * of the stack frame. */ + CompilerDeclareSymbol(c, subScope, param); + } + /* Mark where we are on the stack so we can + * return to it later. */ + sizeBefore = buffer->count; + /* Compile the body in the subscope */ + subOpts.isTail = 1; + CompileBlock(c, subOpts, form, current); + /* Create a new FuncDef as a constant in original scope by splicing + * out the relevant code from the buffer. */ + { + Value newVal; + uint16_t literalIndex; + FuncDef * def = CompilerGenFuncDef(c, buffer->count - sizeBefore, params->count); + /* Add this FuncDef as a literal in the outer scope */ + newVal.type = TYPE_FUNCDEF; + newVal.data.funcdef = def; + literalIndex = CompilerAddLiteral(c, scope, newVal); + /* Generate byteCode to instatiate this FuncDef */ + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, VM_OP_CLN); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, literalIndex); + } + return ret; +} + +/* Branching special */ +static Slot CompileIf(Compiler * c, FormOptions opts, Array * form) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + FormOptions condOpts = FormOptionsDefault(); + FormOptions resOpts = FormOptionsDefault(); + Slot ret = SlotDefault(); + Slot left, right, condition; + uint32_t countAtJump; + uint32_t countAfterFirstBranch; + /* Check argument count */ + if (form->count < 3 || form->count > 4) { + CError(c, "if takes either 2 or 3 arguments"); + } + condition = CompileValue(c, condOpts, form->data[1]); + /* Configure options for bodies of if */ + if (opts.isTail) { + ret.isDud = 1; + resOpts.isTail = 1; + } else if (opts.canDrop) { + ret.isDud = 1; + resOpts.canDrop = 1; + } else if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + resOpts.target = ret.index; + resOpts.canChoose = 0; + /* If we have only one possible body, set + * the result to nil before doing anything. + * Possible optimization - use the condition. */ + if (form->count == 3) { + BufferPushUInt16(gc, buffer, VM_OP_NIL); + BufferPushUInt16(gc, buffer, ret.index); + } + } else { + ret.index = resOpts.target = opts.target; + if (form->count == 3) { + BufferPushUInt16(gc, buffer, VM_OP_NIL); + BufferPushUInt16(gc, buffer, ret.index); + } + } + /* Mark where the buffer is now so we can write the jump + * length later */ + countAtJump = buffer->count; + /* For now use a long if bytecode instruction. + * A short if will probably ususually be sufficient. This + * if byte code will be replaced later with the correct index. */ + BufferPushUInt16(gc, buffer, VM_OP_JIF); + BufferPushUInt16(gc, buffer, condition.index); + BufferPushUInt32(gc, buffer, 0); + /* Compile true path */ + left = CompileValue(c, resOpts, form->data[1]); + if (opts.isTail && !left.isDud) { + BufferPushUInt16(gc, buffer, VM_OP_RET); + BufferPushUInt16(gc, buffer, left.index); + } + CompilerDropSlot(c, scope, left); + /* If we need to jump again, do so */ + if (!opts.isTail && form->count == 4) { + countAtJump = buffer->count; + BufferPushUInt16(gc, buffer, VM_OP_JMP); + BufferPushUInt32(gc, buffer, 0); + } + /* Reinsert jump with correct index */ + countAfterFirstBranch = buffer->count; + buffer->count = countAtJump; + BufferPushUInt16(gc, buffer, VM_OP_JIF); + BufferPushUInt16(gc, buffer, condition.index); + BufferPushUInt32(gc, buffer, (countAfterFirstBranch - countAtJump) / 2); + buffer->count = countAfterFirstBranch; + /* Compile false path */ + if (form->count == 4) { + right = CompileValue(c, resOpts, form->data[1]); + if (opts.isTail && !right.isDud) { + BufferPushUInt16(gc, buffer, VM_OP_RET); + BufferPushUInt16(gc, buffer, right.index); + } + CompilerDropSlot(c, scope, right); + } + /* Set the jump length */ + if (!opts.isTail && form->count == 4) { + countAfterFirstBranch = buffer->count; + buffer->count = countAtJump; + BufferPushUInt16(gc, buffer, VM_OP_JMP); + BufferPushUInt32(gc, buffer, (countAfterFirstBranch - countAtJump) / 2); + buffer->count = countAfterFirstBranch; + } + return ret; +} + +/* Do special */ +static Slot CompileDo(Compiler * c, FormOptions opts, Array * form) { + Slot ret; + CompilerPushScope(c, 1); + ret = CompileBlock(c, opts, form, 1); + CompilerPopScope(c); + return ret; +} + +/* Quote special - returns its argument without parsing. */ +static Slot CompileQuote(Compiler * c, FormOptions opts, Array * form) { + GC * gc = CompilerGC(c); + Scope * scope = c->tail; + Buffer * buffer = c->buffer; + Slot ret = SlotDefault(); + uint16_t literalIndex; + if (form->count != 2) { + CError(c, "Quote takes exactly 1 argument."); + } + Value x = form->data[1]; + if (x.type == TYPE_NIL || + x.type == TYPE_BOOLEAN || + x.type == TYPE_NUMBER) { + return CompileNonReferenceType(c, opts, x); + } + if (opts.canDrop) { + ret.isDud = 1; + return ret; + } + if (opts.canChoose) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + literalIndex = CompilerAddLiteral(c, scope, x); + BufferPushUInt16(gc, buffer, VM_OP_CST); + BufferPushUInt16(gc, buffer, ret.index); + BufferPushUInt16(gc, buffer, literalIndex); + return ret; +} + +/* Assignment special */ +static Slot CompileSet(Compiler * c, FormOptions opts, Array * form) { + if (form->count != 3) { + CError(c, "Assignment expects 2 arguments"); + } + return CompileAssign(c, opts, form->data[1], form->data[2]); +} + +/* Define a function type for Special Form helpers */ +typedef Slot (*SpecialFormHelper) (Compiler * c, FormOptions opts, Array * form); + +/* Dispatch to a special form */ +static SpecialFormHelper GetSpecial(Array * form) { + uint8_t * name; + if (form->count < 1 || form->data[0].type != TYPE_SYMBOL) + return NULL; + name = form->data[0].data.string; + /* If we have a symbol with a zero length name, we have other + * problems. */ + if (VStringSize(name) == 0) + return NULL; + /* One character specials. Mostly math. */ + if (VStringSize(name) == 1) { + switch(name[0]) { + case '+': return CompileAddition; + case '-': return CompileSubtraction; + case '*': return CompileMultiplication; + case '/': return CompileDivision; + case '\'': return CompileQuote; + default: + break; + } + } + /* Multi character specials. Mostly control flow. */ + switch (name[0]) { + case 'd': + { + if (VStringSize(name) == 2 && + name[1] == 'o') { + return CompileDo; + } + } + break; + case 'i': + { + if (VStringSize(name) == 2 && + name[1] == 'f') { + return CompileIf; + } + } + break; + case 'f': + { + if (VStringSize(name) == 2 && + name[1] == 'n') { + return CompileFunction; + } + } + break; + case 'q': + { + if (VStringSize(name) == 5 && + name[1] == 'u' && + name[2] == 'o' && + name[3] == 't' && + name[4] == 'e') { + return CompileQuote; + } + } + break; + case 's': + { + if (VStringSize(name) == 3 && + name[1] == 'e' && + name[2] == 't') { + return CompileSet; + } + } + break; + default: + break; + } + return NULL; +} + +/* Compile a form. Checks for special forms and macros. */ +static Slot CompileForm(Compiler * c, FormOptions opts, Array * form) { + Scope * scope = c->tail; + GC * gc = CompilerGC(c); + Buffer * buffer = c->buffer; + SpecialFormHelper helper; + /* Empty forms evaluate to nil. */ + if (form->count == 0) { + Value temp; + temp.type = TYPE_NIL; + return CompileNonReferenceType(c, opts, temp); + } + /* Check and handle special forms */ + helper = GetSpecial(form); + if (helper != NULL) { + return helper(c, opts, form); + } else { + Slot ret = SlotDefault(); + SlotTracker tracker; + FormOptions subOpts = FormOptionsDefault(); + uint32_t i; + /* Compile all of the arguments */ + CompilerTrackerInit(c, &tracker); + for (i = 0; i < form->count; ++i) { + Slot slot = CompileValue(c, subOpts, form->data[i]); + CompilerTrackerPush(c, &tracker, slot); + } + /* If this is in tail position do a tail call. */ + if (opts.isTail) { + ret.isDud = 1; + BufferPushUInt16(gc, buffer, VM_OP_TCL); + } else { + BufferPushUInt16(gc, buffer, VM_OP_CAL); + if (opts.canDrop) { + ret.isTemp = 1; + ret.index = CompilerGetLocal(c, scope); + } else { + ret.index = opts.target; + } + BufferPushUInt16(gc, buffer, ret.index); + } + /* Push the number of arguments to the function */ + BufferPushUInt16(gc, buffer, form->count - 1); + /* Write the location of all of the arguments */ + CompilerTrackerFree(c, scope, &tracker, 1); + return ret; + } +} + +/* Recursively compile any value or form */ +static Slot CompileValue(Compiler * c, FormOptions opts, Value x) { + switch (x.type) { + case TYPE_NIL: + case TYPE_BOOLEAN: + case TYPE_NUMBER: + return CompileNonReferenceType(c, opts, x); + case TYPE_SYMBOL: + return CompileSymbol(c, opts, x); + case TYPE_FORM: + return CompileForm(c, opts, x.data.array); + case TYPE_ARRAY: + return CompileArray(c, opts, x.data.array); + case TYPE_DICTIONARY: + return CompileDict(c, opts, x.data.dict); + default: + return CompileLiteral(c, opts, x); + } +} + +/* Initialize a Compiler struct */ +void CompilerInit(Compiler * c, VM * vm) { + c->vm = vm; + c->buffer = BufferNew(&vm->gc, 128); + c->env = ArrayNew(&vm->gc, 10); + c->tail = c->root = NULL; + c->error = NULL; + CompilerPushScope(c, 0); +} + +/* Register a global for the compilation environment. */ +void CompilerAddGlobal(Compiler * c, const char * name, Value x) { + GC * gc = CompilerGC(c); + Value sym = ValueLoadCString(CompilerGC(c), name); + sym.type = TYPE_SYMBOL; + CompilerDeclareSymbol(c, c->root, sym); + ArrayPush(gc, c->env, x); +} + +/* Compile interface. Returns a function that evaluates the + * given AST. Returns NULL if there was an error during compilation. */ +Func * CompilerCompile(Compiler * c, Value form) { + GC * gc = CompilerGC(c); + FormOptions opts = FormOptionsDefault(); + FuncDef * def; + if (setjmp(c->onError)) { + /* Clear all but root scope */ + c->tail = c->root; + c->root->nextScope = NULL; + return NULL; + } + /* Create a scope */ + opts.isTail = 1; + CompilerPushScope(c, 0); + CompilerReturnSlot(c, CompileValue(c, opts, form)); + def = CompilerGenFuncDef(c, c->buffer->count, 0); + { + uint32_t envSize = c->env->count; + FuncEnv * env = GCAlloc(gc, sizeof(FuncEnv)); + Func * func = GCAlloc(gc, sizeof(Func)); + env->values = GCAlloc(gc, sizeof(Value) * envSize); + memcpy(env->values, c->env->data, envSize * sizeof(Value)); + env->thread = NULL; + func->parent = NULL; + func->def = def; + func->env = env; + return func; + } +} diff --git a/compile.h b/compile.h new file mode 100644 index 00000000..033f159d --- /dev/null +++ b/compile.h @@ -0,0 +1,15 @@ +#ifndef COMPILE_H_9VXF71HY +#define COMPILE_H_9VXF71HY + +#include "datatypes.h" + +/* Initialize the Compiler */ +void CompilerInit(Compiler * c, VM * vm); + +/* Register a global for the compilation environment. */ +void CompilerAddGlobal(Compiler * c, const char * name, Value x); + +/* Compile a function that evaluates the given form. */ +Func * CompilerCompile(Compiler * c, Value form); + +#endif /* end of include guard: COMPILE_H_9VXF71HY */ diff --git a/datatypes.h b/datatypes.h new file mode 100644 index 00000000..e7ff09eb --- /dev/null +++ b/datatypes.h @@ -0,0 +1,185 @@ +#ifndef DATATYPES_H_PJJ035NT +#define DATATYPES_H_PJJ035NT + +#include +#include + +#define THREAD_STATUS_ALIVE 0 +#define THREAD_STATUS_DEAD 1 +#define THREAD_STATUS_PENDING 2 + +typedef enum Type { + TYPE_NIL = 0, + TYPE_NUMBER, + TYPE_BOOLEAN, + TYPE_STRING, + TYPE_SYMBOL, + TYPE_ARRAY, + TYPE_THREAD, + TYPE_FORM, + TYPE_BYTEBUFFER, + TYPE_FUNCTION, + TYPE_CFUNCTION, + TYPE_DICTIONARY, + TYPE_FUNCDEF, + TYPE_FUNCENV +} Type; + +typedef double Number; +typedef uint8_t Boolean; +typedef struct VM VM; +typedef struct Value Value; +typedef Value (*CFunction)(VM * vm); +typedef struct Func Func; +typedef struct FuncDef FuncDef; +typedef struct FuncEnv FuncEnv; +typedef union ValueData ValueData; +typedef struct DictBucket DictBucket; +typedef struct Array Array; +typedef struct Buffer Buffer; +typedef struct Dictionary Dictionary; +typedef struct DictionaryIterator DictionaryIterator; +typedef struct GC GC; +typedef struct Parser Parser; +typedef struct ParseState ParseState; +typedef struct Scope Scope; +typedef struct Compiler Compiler; + +union ValueData { + Boolean boolean; + Number number; + uint8_t * string; + Array * array; + Buffer * buffer; + Dictionary * dict; + Func * func; + void * pointer; + FuncDef * funcdef; + FuncEnv * funcenv; + CFunction cfunction; + uint16_t u16[4]; + uint8_t u8[8]; +} data; + +/* Use an Array to represent the stack. A Stack frame is + * represented by a grouping of FRAME_SIZE values. */ +#define FRAME_SIZE 4 + +#define ThreadStack(t) ((t)->data + (t)->count) + +#define FrameMeta(t) (ThreadStack(t)[-1]) +#define FrameReturn(t) ((ThreadStack(t) - 1)->data.u16[0]) +#define FrameSize(t) ((ThreadStack(t) - 1)->data.u16[1]) +#define FramePrevSize(t) ((ThreadStack(t) - 1)->data.u16[2]) + +#define FrameCallee(t) (ThreadStack(t)[-2]) +#define FrameEnvValue(t) (ThreadStack(t)[-3]) +#define FrameEnv(t) ((ThreadStack(t) - 3)->data.funcenv) +#define FramePCValue(t) (ThreadStack(t)[-4]) +#define FramePC(t) ((ThreadStack(t)[-1]).data.pointer) + +struct Array { + uint32_t count; + uint32_t capacity; + Value * data; +}; + +struct Buffer { + uint32_t count; + uint32_t capacity; + uint8_t * data; +}; + +struct Dictionary { + uint32_t count; + uint32_t capacity; + DictBucket ** buckets; +}; + +struct DictionaryIterator { + Dictionary * dict; + uint32_t index; + DictBucket * bucket; +}; + +struct FuncDef { + uint32_t locals; + uint32_t arity; + uint32_t literalsLen; + uint32_t byteCodeLen; + Value * literals; /* Contains strings, FuncDefs, etc. */ + uint16_t * byteCode; +}; + +struct FuncEnv { + Array * thread; /* When nil, index the local values */ + uint32_t stackOffset; /* Used as environment size when off stack */ + Value * values; +}; + +struct Func { + FuncDef * def; + FuncEnv * env; + Func * parent; +}; + +struct Value { + Type type; + ValueData data; +}; + +struct DictBucket { + Value key; + Value value; + DictBucket * next; +}; + +struct GC { + void * blocks; + void * user; + void (*handleOutOfMemory)(GC * gc); + uint32_t memoryInterval; + uint32_t nextCollection; + uint32_t black : 1; +}; + +struct VM { + GC gc; + const char * error; + uint16_t * pc; + Array * thread; + Value * base; + jmp_buf jump; + Value tempRoot; /* Temporary GC root */ +}; + +/* Parsing */ + +#define PARSER_PENDING 0 +#define PARSER_FULL 1 +#define PARSER_ERROR -1 + +struct Parser { + VM * vm; + const char * error; + ParseState * data; + Value value; + uint32_t count; + uint32_t cap; + uint32_t index; + uint32_t status; +}; + +/* Compiling */ + +struct Compiler { + VM * vm; + const char * error; + jmp_buf onError; + Scope * root; + Scope * tail; + Array * env; + Buffer * buffer; +}; + +#endif /* end of include guard: DATATYPES_H_PJJ035NT */ diff --git a/dict.c b/dict.c new file mode 100644 index 00000000..29d3a698 --- /dev/null +++ b/dict.c @@ -0,0 +1,152 @@ +#include "dict.h" +#include "value.h" +#include "gc.h" + +/* Create a new dictionary */ +Dictionary * DictNew(GC * gc, uint32_t capacity) { + Dictionary * dict = GCAlloc(gc, sizeof(Dictionary)); + DictBucket ** buckets = GCZalloc(gc, capacity * sizeof(DictBucket *)); + dict->buckets = buckets; + dict->capacity = capacity; + dict->count = 0; + return dict; +} + +/* Resize the dictionary table. */ +static void DictReHash(GC * gc, Dictionary * dict, uint32_t size) { + DictBucket ** newBuckets = GCZalloc(gc, size * sizeof(DictBucket *)); + uint32_t i, count; + for (i = 0, count = dict->capacity; i < count; ++i) { + DictBucket * bucket = dict->buckets[i]; + while (bucket) { + uint32_t index; + DictBucket * next = bucket->next; + index = ValueHash(&bucket->key) % size; + bucket->next = newBuckets[index]; + newBuckets[index] = bucket; + bucket = next; + } + } + dict->buckets = newBuckets; + dict->capacity = size; +} + +/* Find the bucket that contains the given key */ +static DictBucket * DictFind(Dictionary * dict, Value * key) { + uint32_t index = ValueHash(key) % dict->capacity; + DictBucket * bucket = dict->buckets[index]; + while (bucket) { + if (ValueEqual(&bucket->key, key)) + return bucket; + bucket = bucket->next; + } + return (DictBucket *)0; +} + +/* Get a value out of the dictionary */ +Value DictGet(Dictionary * dict, Value * key) { + DictBucket * bucket = DictFind(dict, key); + if (bucket) { + return bucket->value; + } else { + Value nil; + nil.type = TYPE_NIL; + return nil; + } +} + +/* Remove an entry from the dictionary */ +Value DictRemove(GC * gc, Dictionary * dict, Value * key) { + DictBucket * bucket, * previous; + uint32_t index = ValueHash(key) % dict->capacity; + bucket = dict->buckets[index]; + previous = (DictBucket *)0; + while (bucket) { + if (ValueEqual(&bucket->key, key)) { + if (previous) { + previous->next = bucket->next; + } else { + dict->buckets[index] = bucket->next; + } + if (dict->count < dict->capacity / 4) { + DictReHash(gc, dict, dict->capacity / 2); + } + --dict->count; + return bucket->value; + } + previous = bucket; + bucket = bucket->next; + } + /* Return nil if we found nothing */ + { + Value nil; + nil.type = TYPE_NIL; + return nil; + } +} + +/* Put a value into the dictionary. Returns 1 if successful, 0 if out of memory. + * The VM pointer is needed for memory allocation. */ +void DictPut(GC * gc, Dictionary * dict, Value * key, Value * value) { + DictBucket * bucket, * previous; + uint32_t index = ValueHash(key) % dict->capacity; + if (key->type == TYPE_NIL) return; + /* Do a removal if value is nil */ + if (value->type == TYPE_NIL) { + bucket = dict->buckets[index]; + previous = (DictBucket *)0; + while (bucket) { + if (ValueEqual(&bucket->key, key)) { + if (previous) { + previous->next = bucket->next; + } else { + dict->buckets[index] = bucket->next; + } + if (dict->count < dict->capacity / 4) { + DictReHash(gc, dict, dict->capacity / 2); + } + --dict->count; + return; + } + previous = bucket; + bucket = bucket->next; + } + } else { + bucket = DictFind(dict, key); + if (bucket) { + bucket->value = *value; + } else { + if (dict->count >= 2 * dict->capacity) { + DictReHash(gc, dict, 2 * dict->capacity); + } + bucket = GCAlloc(gc, sizeof(DictBucket)); + bucket->next = dict->buckets[index]; + bucket->value = *value; + bucket->key = *key; + dict->buckets[index] = bucket; + ++dict->count; + } + } +} + +/* Begin iteration through a dictionary */ +void DictIterate(Dictionary * dict, DictionaryIterator * iterator) { + iterator->index = 0; + iterator->dict = dict; + iterator->bucket = dict->buckets[0]; +} + +/* Provides a mechanism for iterating through a table. */ +int DictIterateNext(DictionaryIterator * iterator, DictBucket ** bucket) { + Dictionary * dict = iterator->dict; + for (;;) { + if (iterator->bucket) { + *bucket = iterator->bucket; + iterator->bucket = iterator->bucket->next; + return 1; + } + if (++iterator->index >= dict->capacity) break; + iterator->bucket = dict->buckets[iterator->index]; + } + return 0; +} diff --git a/dict.h b/dict.h new file mode 100644 index 00000000..59073209 --- /dev/null +++ b/dict.h @@ -0,0 +1,26 @@ +#ifndef DICT_H_YN2BKHUQ +#define DICT_H_YN2BKHUQ + +#include "datatypes.h" + +/* Create a new dictionary */ +Dictionary * DictNew(GC * gc, uint32_t capacity); + +/* Get a value out of the dictionary */ +Value DictGet(Dictionary * dict, Value * key); + +/* Get a Value from the dictionary, but remove it at the same + * time. */ +Value DictRemove(GC * gc, Dictionary * dict, Value * key); + +/* Put a value into the dictionary. Returns 1 if successful, 0 if out of memory. + * The VM pointer is needed for memory allocation. */ +void DictPut(GC * gc, Dictionary * dict, Value * key, Value * value); + +/* Begin iteration through a dictionary */ +void DictIterate(Dictionary * dict, DictionaryIterator * iterator); + +/* Provides a mechanism for iterating through a table. */ +int DictIterateNext(DictionaryIterator * iterator, DictBucket ** bucket); + +#endif /* end of include guard: DICT_H_YN2BKHUQ */ diff --git a/gc.c b/gc.c new file mode 100644 index 00000000..063de728 --- /dev/null +++ b/gc.c @@ -0,0 +1,209 @@ +#include +#include "gc.h" +#include "dict.h" +#include "vstring.h" +#include "parse.h" + +#define GCHeader(mem) ((GCMemoryHeader *)(mem) - 1) + +typedef struct GCMemoryHeader GCMemoryHeader; +struct GCMemoryHeader { + GCMemoryHeader * next; + uint32_t color; +}; + +/* Initialize a garbage collector */ +void GCInit(GC * gc, uint32_t memoryInterval) { + gc->black = 0; + gc->blocks = NULL; + gc->nextCollection = 0; + gc->memoryInterval = memoryInterval; + gc->handleOutOfMemory = NULL; +} + +/* Mark some raw memory */ +void GCMarkMemory(GC * gc, void * memory) { + GCHeader(memory)->color = gc->black; +} + +/* Helper to mark function environments */ +static void GCMarkFuncEnv(GC * gc, FuncEnv * env) { + if (GCHeader(env)->color != gc->black) { + Value temp; + GCHeader(env)->color = gc->black; + if (env->thread) { + temp.type = TYPE_THREAD; + temp.data.array = env->thread; + GCMark(gc, &temp); + } else { + uint32_t count = env->stackOffset; + uint32_t i; + GCHeader(env->values)->color = gc->black; + for (i = 0; i < count; ++i) { + GCMark(gc, env->values + i); + } + } + } +} + +/* Mark allocated memory associated with a value. This is + * the main function for doing garbage collection. */ +void GCMark(GC * gc, Value * x) { + switch (x->type) { + case TYPE_NIL: + case TYPE_BOOLEAN: + case TYPE_NUMBER: + case TYPE_CFUNCTION: + break; + + case TYPE_STRING: + case TYPE_SYMBOL: + GCHeader(VStringRaw(x->data.string))->color = gc->black; + break; + + case TYPE_BYTEBUFFER: + GCHeader(x->data.buffer)->color = gc->black; + GCHeader(x->data.buffer->data)->color = gc->black; + break; + + case TYPE_ARRAY: + case TYPE_FORM: + if (GCHeader(x->data.array)->color != gc->black) { + uint32_t i, count; + count = x->data.array->count; + GCHeader(x->data.array)->color = gc->black; + GCHeader(x->data.array->data)->color = gc->black; + for (i = 0; i < count; ++i) + GCMark(gc, x->data.array->data + i); + } + break; + + case TYPE_THREAD: + if (GCHeader(x->data.array)->color != gc->black) { + uint32_t i, count; + count = x->data.array->count; + GCHeader(x->data.array)->color = gc->black; + GCHeader(x->data.array->data)->color = gc->black; + if (count) { + count += FrameSize(x->data.array); + for (i = 0; i < count; ++i) + GCMark(gc, x->data.array->data + i); + } + } + break; + + case TYPE_FUNCTION: + if (GCHeader(x->data.func)->color != gc->black) { + Func * f = x->data.func; + GCHeader(f)->color = gc->black; + GCMarkFuncEnv(gc, f->env); + { + Value temp; + temp.type = TYPE_FUNCDEF; + temp.data.funcdef = x->data.funcdef; + GCMark(gc, &temp); + if (f->parent) { + temp.type = TYPE_FUNCTION; + temp.data.func = f->parent; + GCMark(gc, &temp); + } + } + } + break; + + case TYPE_DICTIONARY: + if (GCHeader(x->data.dict)->color != gc->black) { + DictionaryIterator iter; + DictBucket * bucket; + GCHeader(x->data.dict)->color = gc->black; + GCHeader(x->data.dict->buckets)->color = gc->black; + DictIterate(x->data.dict, &iter); + while (DictIterateNext(&iter, &bucket)) { + GCHeader(bucket)->color = gc->black; + GCMark(gc, &bucket->key); + GCMark(gc, &bucket->value); + } + } + break; + + case TYPE_FUNCDEF: + if (GCHeader(x->data.funcdef)->color != gc->black) { + GCHeader(x->data.funcdef->byteCode)->color = gc->black; + uint32_t count, i; + count = x->data.funcdef->literalsLen; + if (x->data.funcdef->literals) { + GCHeader(x->data.funcdef->literals)->color = gc->black; + for (i = 0; i < count; ++i) + GCMark(gc, x->data.funcdef->literals + i); + } + } + break; + + case TYPE_FUNCENV: + GCMarkFuncEnv(gc, x->data.funcenv); + break; + + } + +} + +/* Iterate over all allocated memory, and free memory that is not + * marked as reachable. Flip the gc color flag for next sweep. */ +void GCSweep(GC * gc) { + GCMemoryHeader * previous = NULL; + GCMemoryHeader * current = gc->blocks; + while (current) { + if (current->color != gc->black) { + if (previous) { + previous->next = current->next; + } else { + gc->blocks = current->next; + } + free(current); + } else { + previous = current; + } + current = current->next; + } + /* Rotate flag */ + gc->black = !gc->black; +} + +/* Clean up all memory */ +void GCClear(GC * gc) { + GCMemoryHeader * current = gc->blocks; + while (current) { + GCMemoryHeader * next = current->next; + free(current); + current = next; + } + gc->blocks = NULL; +} + +/* Prepare a memory block */ +static void * GCPrepare(GC * gc, char * rawBlock, uint32_t size) { + GCMemoryHeader * mdata; + if (rawBlock == NULL) { + if (gc->handleOutOfMemory != NULL) + gc->handleOutOfMemory(gc); + return NULL; + } + gc->nextCollection += size; + mdata = (GCMemoryHeader *) rawBlock; + mdata->next = gc->blocks; + gc->blocks = mdata; + mdata->color = !gc->black; + return rawBlock + sizeof(GCMemoryHeader); +} + +/* Allocate some memory that is tracked for garbage collection */ +void * GCAlloc(GC * gc, uint32_t size) { + uint32_t totalSize = size + sizeof(GCMemoryHeader); + return GCPrepare(gc, malloc(totalSize), totalSize); +} + +/* Allocate some zeroed memory that is tracked for garbage collection */ +void * GCZalloc(GC * gc, uint32_t size) { + uint32_t totalSize = size + sizeof(GCMemoryHeader); + return GCPrepare(gc, calloc(1, totalSize), totalSize); +} diff --git a/gc.h b/gc.h new file mode 100644 index 00000000..99875c51 --- /dev/null +++ b/gc.h @@ -0,0 +1,41 @@ +#ifndef GC_H_N8L3U4KK +#define GC_H_N8L3U4KK + +#include "datatypes.h" + +/* Initialize a GC */ +void GCInit(GC * gc, uint32_t memoryInterval); + +/* Iterate over all allocated memory, and frees memory that is not + * marked as reachable */ +void GCSweep(GC * gc); + +/* Do a depth first search of the variables and mark all reachable memory. + * Root variables are just everyting in the stack. */ +void GCMark(GC * gc, Value * x); + +/* Mark some raw memory as reachable */ +void GCMarkMemory(GC * gc, void * memory); + +/* Clean up all memory, including locked memory */ +void GCClear(GC * gc); + +/* Allocate some memory that is tracked for garbage collection */ +void * GCAlloc(GC * gc, uint32_t size); + +/* Allocate zeroed memory */ +void * GCZalloc(GC * gc, uint32_t size); + +/* Run a collection */ +#define GCCollect(gc, root) \ + (GCMark((gc), (root)), GCSweep(gc), (gc)->nextCollection = 0) + +/* Check if a collection needs to be run */ +#define GCNeedsCollect(gc) \ + ((gc)->nextCollection >= (gc)->memoryInterval) + +/* Run a collection if enough memory has been allocated since last collection */ +#define GCMaybeCollect(gc, root) \ + (GCNeedsCollect(gc) ? GCCollect((gc), (root)) : 0) + +#endif /* end of include guard: GC_H_N8L3U4KK */ diff --git a/main.c b/main.c new file mode 100644 index 00000000..1101377c --- /dev/null +++ b/main.c @@ -0,0 +1,90 @@ +#include +#include +#include "datatypes.h" +#include "gc.h" +#include "vm.h" +#include "parse.h" +#include "compile.h" +#include "value.h" + +/* A simple repl for debugging */ +void debugRepl() { + char buffer[128] = {0}; + const char * reader = buffer; + Func * func; + VM vm; + Parser p; + Compiler c; + + VMInit(&vm); + + for (;;) { + + /* Run garbage collection */ + VMMaybeCollect(&vm); + + /* Reset state */ + ParserInit(&p, &vm); + + /* Get and parse input until we have a full form */ + while (p.status == PARSER_PENDING) { + /* Get some input if we are done */ + if (*reader == '\0') { + printf(">> "); + if (!fgets(buffer, sizeof(buffer), stdin)) { + return; + } + p.index = 0; + reader = buffer; + } + reader += ParserParseCString(&p, reader); + } + + /* Check for parsing errors */ + if (p.error) { + unsigned i; + printf("\n"); + printf("%s\n", buffer); + for (i = 0; i < p.index; ++i) { + printf(" "); + } + printf("^\n"); + printf("\nParse error: %s\n", p.error); + reader = buffer; /* Flush the input buffer */ + buffer[0] = '\0'; + continue; + } + + /* Try to compile generated AST */ + CompilerInit(&c, &vm); + func = CompilerCompile(&c, p.value); + + /* Check for compilation errors */ + if (c.error) { + printf("Compiler error: %s\n", c.error); + reader = buffer; + buffer[0] = 0; + continue; + } else { + printf("Compiled!\n"); + } + + /* Execute function */ + VMLoad(&vm, func); + if (VMStart(&vm)) { + printf("VM error: %s\n", vm.error); + reader = buffer; + buffer[0] = 0; + continue; + } else { + ValuePrint(&vm.tempRoot, 0); + printf("\n"); + } + } + +} + +int main() { + printf("Super cool interpreter v0.0\n"); + debugRepl(); +} diff --git a/opcodes.h b/opcodes.h new file mode 100644 index 00000000..5a064efd --- /dev/null +++ b/opcodes.h @@ -0,0 +1,40 @@ +#ifndef OPCODES_H_EFPEYNZ0 +#define OPCODES_H_EFPEYNZ0 + +enum OpCode { + VM_OP_ADD = 0, /* 0x0000 */ + VM_OP_SUB, /* 0x0001 */ + VM_OP_MUL, /* 0x0002 */ + VM_OP_DIV, /* 0x0003 */ + VM_OP_NOT, /* 0x0004 */ + VM_OP_LD0, /* 0x0005 */ + VM_OP_LD1, /* 0x0006 */ + VM_OP_FLS, /* 0x0007 */ + VM_OP_TRU, /* 0x0008 */ + VM_OP_NIL, /* 0x0009 */ + VM_OP_I16, /* 0x000a */ + VM_OP_UPV, /* 0x000b */ + VM_OP_JIF, /* 0x000c */ + VM_OP_JMP, /* 0x000d */ + VM_OP_CAL, /* 0x000e */ + VM_OP_RET, /* 0x000f */ + VM_OP_SUV, /* 0x0010 */ + VM_OP_CST, /* 0x0011 */ + VM_OP_I32, /* 0x0012 */ + VM_OP_F64, /* 0x0013 */ + VM_OP_MOV, /* 0x0014 */ + VM_OP_CLN, /* 0x0015 */ + VM_OP_EQL, /* 0x0016 */ + VM_OP_LTN, /* 0x0017 */ + VM_OP_LTE, /* 0x0018 */ + VM_OP_ARR, /* 0x0019 */ + VM_OP_DIC, /* 0x001a */ + VM_OP_TCL, /* 0x001b */ + VM_OP_ADM, /* 0x001c */ + VM_OP_SBM, /* 0x001d */ + VM_OP_MUM, /* 0x001e */ + VM_OP_DVM, /* 0x001f */ + VM_OP_RTN /* 0x0020 */ +}; + +#endif /* end of include guard: OPCODES_H_EFPEYNZ0 */ diff --git a/parse.c b/parse.c new file mode 100644 index 00000000..9fde27f6 --- /dev/null +++ b/parse.c @@ -0,0 +1,456 @@ +#include +#include +#include +#include +#include "datatypes.h" +#include "array.h" +#include "dict.h" +#include "gc.h" +#include "buffer.h" +#include "parse.h" +#include "vm.h" +#include "vstring.h" + +static const char UNEXPECTED_CLOSING_DELIM[] = "Unexpected closing delimiter"; + +/* The type of a ParseState */ +typedef enum ParseType { + PTYPE_ROOT, + PTYPE_ARRAY, + PTYPE_FORM, + PTYPE_DICTIONARY, + PTYPE_STRING, + PTYPE_TOKEN +} ParseType; + +/* Contain a parse state that goes on the parse stack */ +struct ParseState { + ParseType type; + union { + Array * array; + struct { + Dictionary * dict; + Value key; + int keyFound; + } dictState; + struct { + Buffer * buffer; + enum { + STRING_STATE_BASE, + STRING_STATE_ESCAPE, + STRING_STATE_ESCAPE_UNICODE, + STRING_STATE_ESCAPE_HEX + } state; + } string; + } buf; +}; + +/* Handle error in parsing */ +#define PError(p, e) ((p)->error = (e), (p)->status = PARSER_ERROR) + +/* Get the top ParseState in the parse stack */ +static ParseState * ParserPeek(Parser * p) { + if (!p->count) { + PError(p, "Parser stack underflow. (Peek)"); + return NULL; + } + return p->data + p->count - 1; +} + +/* Remove the top state from the ParseStack */ +static ParseState * ParserPop(Parser * p) { + if (!p->count) { + PError(p, "Parser stack underflow. (Pop)"); + return NULL; + } + return p->data + --p->count; +} + +/* Add a new, empty ParseState to the ParseStack. */ +static void ParserPush(Parser *p, ParseType type) { + GC * gc = &p->vm->gc; + ParseState * top; + if (p->count >= p->cap) { + uint32_t newCap = 2 * p->count; + ParseState * data = GCAlloc(gc, newCap); + p->data = data; + p->cap = newCap; + } + ++p->count; + top = ParserPeek(p); + if (!top) return; + top->type = type; + switch (type) { + case PTYPE_ROOT: + break; + case PTYPE_STRING: + top->buf.string.state = STRING_STATE_BASE; + case PTYPE_TOKEN: + top->buf.string.buffer = BufferNew(gc, 10); + break; + case PTYPE_ARRAY: + case PTYPE_FORM: + top->buf.array = ArrayNew(gc, 10); + break; + case PTYPE_DICTIONARY: + top->buf.dictState.dict = DictNew(gc, 10); + top->buf.dictState.keyFound = 0; + break; + } +} + +/* Append a value to the top-most state in the Parser's stack. */ +static void ParserTopAppend(Parser * p, Value x) { + GC * gc = &p->vm->gc; + ParseState * top = ParserPeek(p); + if (!top) return; + switch (top->type) { + case PTYPE_ROOT: + p->value = x; + p->status = PARSER_FULL; + break; + case PTYPE_ARRAY: + case PTYPE_FORM: + ArrayPush(gc, top->buf.array, x); + break; + case PTYPE_DICTIONARY: + if (top->buf.dictState.keyFound) { + DictPut(gc, top->buf.dictState.dict, &top->buf.dictState.key, &x); + } else { + top->buf.dictState.key = x; + } + top->buf.dictState.keyFound = !top->buf.dictState.keyFound; + break; + default: + PError(p, "Expected container type."); + break; + } +} + +/* Check if a character is whitespace */ +static int isWhitespace(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\0' || c == ','; +} + +/* Check if a character is a valid symbol character */ +static int isSymbolChar(uint8_t c) { + if (c >= 'a' && c <= 'z') return 1; + if (c >= 'A' && c <= 'Z') return 1; + if (c >= '0' && c <= '9') return 1; + if (c >= '<' && c <= '@') return 1; + if (c >= '*' && c <= '/') return 1; + if (c >= '#' && c <= '&') return 1; + if (c == '_') return 1; + if (c == '^') return 1; + if (c == '!') return 1; + return 0; +} + +/* Get an integer power of 10 */ +static double exp10(int power) { + if (power == 0) return 1; + if (power > 0) { + double result = 10; + int currentPower = 1; + while (currentPower * 2 <= power) { + result = result * result; + currentPower *= 2; + } + return result * exp10(power - currentPower); + } else { + return 1 / exp10(-power); + } +} + +/* Read a number from a string */ +static int ParseReadNumber(const uint8_t * string, const uint8_t * end, double * ret, int forceInt) { + int sign = 1, x = 0; + double accum = 0, exp = 1, place = 1; + if (*string == '-') { + sign = -1; + ++string; + } else if (*string == '+') { + ++string; + } + if (string >= end) return 0; + while (string < end) { + if (*string == '.' && !forceInt) { + place = 0.1; + } else if (!forceInt && (*string == 'e' || *string == 'E')) { + ++string; + if (!ParseReadNumber(string, end, &exp, 1)) + return 0; + exp = exp10(exp); + break; + } else { + x = *string; + if (x < '0' || x > '9') return 0; + x -= '0'; + if (place < 1) { + accum += x * place; + place *= 0.1; + } else { + accum *= 10; + accum += x; + } + } + ++string; + } + *ret = accum * sign * exp; + return 1; +} + +/* Checks if a string slice is equal to a string constant */ +static int checkStrConst(const char * ref, const uint8_t * start, const uint8_t * end) { + while (*ref && start < end) { + if (*ref != *(char *)start) return 0; + ++ref; + ++start; + } + return !*ref && start == end; +} + +/* Handle parsing generic input */ +static int ParserMainState(Parser * p, uint8_t c) { + if (c == '(') { + ParserPush(p, PTYPE_FORM); + return 1; + } + if (c == '[') { + ParserPush(p, PTYPE_ARRAY); + return 1; + } + if (c == '{') { + ParserPush(p, PTYPE_DICTIONARY); + return 1; + } + if (c == '"') { + ParserPush(p, PTYPE_STRING); + return 1; + } + if (isWhitespace(c)) return 1; + if (isSymbolChar(c)) { + ParserPush(p, PTYPE_TOKEN); + return 0; + } + PError(p, "Unexpected character."); + return 1; +} + +/* Build from the token buffer */ +static Value ParserBuildTokenBuffer(Parser * p, Buffer * buf) { + Value x; + Number number; + uint8_t * data = buf->data; + uint8_t * back = data + buf->count; + if (ParseReadNumber(data, back, &number, 0)) { + x.type = TYPE_NUMBER; + x.data.number = number; + } else if (checkStrConst("nil", data, back)) { + x.type = TYPE_NIL; + x.data.boolean = 0; + } else if (checkStrConst("false", data, back)) { + x.type = TYPE_BOOLEAN; + x.data.boolean = 0; + } else if (checkStrConst("true", data, back)) { + x.type = TYPE_BOOLEAN; + x.data.boolean = 1; + } else { + if (buf->data[0] >= '0' && buf->data[0] <= '9') { + PError(p, "Symbols cannot start with digits."); + x.type = TYPE_NIL; + } else { + x.type = TYPE_SYMBOL; + x.data.string = BufferToString(&p->vm->gc, buf); + } + } + return x; +} + +/* Handle parsing a token */ +static int ParserTokenState(Parser * p, uint8_t c) { + ParseState * top = ParserPeek(p); + Buffer * buf = top->buf.string.buffer; + if (isWhitespace(c) || c == ')' || c == ']' || c == '}') { + ParserPop(p); + ParserTopAppend(p, ParserBuildTokenBuffer(p, buf)); + return !(c == ')' || c == ']' || c == '}'); + } else if (isSymbolChar(c)) { + BufferPush(&p->vm->gc, buf, c); + return 1; + } else { + PError(p, "Expected symbol character."); + return 1; + } +} + +/* Handle parsing a string literal */ +static int ParserStringState(Parser * p, uint8_t c) { + ParseState * top = ParserPeek(p); + switch (top->buf.string.state) { + case STRING_STATE_BASE: + if (c == '\\') { + top->buf.string.state = STRING_STATE_ESCAPE; + } else if (c == '"') { + Value x; + x.type = TYPE_STRING; + x.data.string = BufferToString(&p->vm->gc, top->buf.string.buffer); + ParserPop(p); + ParserTopAppend(p, x); + } else { + BufferPush(&p->vm->gc, top->buf.string.buffer, c); + } + break; + case STRING_STATE_ESCAPE: + { + uint8_t next; + switch (c) { + case 'n': next = '\n'; break; + case 'r': next = '\r'; break; + case 't': next = '\t'; break; + case 'f': next = '\f'; break; + case '0': next = '\0'; break; + case '"': next = '"'; break; + case '\'': next = '\''; break; + case 'z': next = '\0'; break; + default: + PError(p, "Unknown string escape sequence."); + return 1; + } + BufferPush(&p->vm->gc, top->buf.string.buffer, next); + top->buf.string.state = STRING_STATE_BASE; + } + break; + case STRING_STATE_ESCAPE_HEX: + break; + case STRING_STATE_ESCAPE_UNICODE: + break; + } + return 1; +} + +/* Handle parsing a form */ +static int ParserFormState(Parser * p, uint8_t c) { + if (c == ')') { + ParseState * top = ParserPop(p); + Array * array = top->buf.array; + Value x; + x.type = TYPE_FORM; + x.data.array = array; + ParserTopAppend(p, x); + return 1; + } else if (c == ']' || c == '}') { + PError(p, UNEXPECTED_CLOSING_DELIM); + return 1; + } else { + return ParserMainState(p, c); + } +} + +/* Handle parsing an array */ +static int ParserArrayState(Parser * p, uint8_t c) { + if (c == ']') { + ParseState * top = ParserPop(p); + Array * array = top->buf.array; + Value x; + x.type = TYPE_ARRAY; + x.data.array = array; + ParserTopAppend(p, x); + return 1; + } else if (c == ')' || c == '}') { + PError(p, UNEXPECTED_CLOSING_DELIM); + return 1; + } else { + return ParserMainState(p, c); + } +} + +/* Handle parsing a dictionary */ +static int ParserDictState(Parser * p, uint8_t c) { + if (c == '}') { + ParseState * top = ParserPop(p); + if (!top->buf.dictState.keyFound) { + Value x; + x.type = TYPE_DICTIONARY; + x.data.dict = top->buf.dictState.dict; + ParserTopAppend(p, x); + return 1; + } else { + PError(p, "Odd number of items in dictionary literal."); + return 1; + } + } else if (c == ')' || c == ']') { + PError(p, UNEXPECTED_CLOSING_DELIM); + return 1; + } else { + return ParserMainState(p, c); + } +} + +/* Root state of the parser */ +static int ParserRootState(Parser * p, uint8_t c) { + if (c == ']' || c == ')' || c == '}') { + PError(p, UNEXPECTED_CLOSING_DELIM); + return 1; + } else { + return ParserMainState(p, c); + } +} + +/* Handle a character */ +static int ParserDispatchChar(Parser * p, uint8_t c) { + int done = 0; + while (!done && p->status == PARSER_PENDING) { + ParseState * top = ParserPeek(p); + switch (top->type) { + case PTYPE_ROOT: + done = ParserRootState(p, c); + break; + case PTYPE_TOKEN: + done = ParserTokenState(p, c); + break; + case PTYPE_FORM: + done = ParserFormState(p, c); + break; + case PTYPE_ARRAY: + done = ParserArrayState(p, c); + break; + case PTYPE_STRING: + done = ParserStringState(p, c); + break; + case PTYPE_DICTIONARY: + done = ParserDictState(p, c); + break; + } + } + ++p->index; + return !done; +} + +/* Parse a C style string. The first value encountered when parsed is put + * in p->value. The string variable is then updated to the next char that + * was not read. Returns 1 if any values were read, otherwise returns 0. + * Returns the number of bytes read. + */ +int ParserParseCString(Parser * p, const char * string) { + int bytesRead = 0; + p->status = PARSER_PENDING; + while ((p->status == PARSER_PENDING) && (string[bytesRead] != '\0')) { + ParserDispatchChar(p, string[bytesRead++]); + } + return bytesRead; +} + +/* Parser initialization (memory allocation) */ +void ParserInit(Parser * p, VM * vm) { + p->vm = vm; + ParseState * data = GCAlloc(&vm->gc, sizeof(ParseState) * 10); + p->data = data; + p->count = 0; + p->cap = 10; + p->index = 0; + p->error = NULL; + p->status = PARSER_PENDING; + p->value.type = TYPE_NIL; + ParserPush(p, PTYPE_ROOT); +} diff --git a/parse.h b/parse.h new file mode 100644 index 00000000..80a4cee1 --- /dev/null +++ b/parse.h @@ -0,0 +1,14 @@ +#ifndef PARSE_H_ONYWMADW +#define PARSE_H_ONYWMADW + +#include "datatypes.h" + +#define PARSE_ERROR -1 +#define PARSE_VALUE_READ 1 +#define PARSE_VALUE_PENDING 0 + +void ParserInit(Parser * p, VM * vm); + +int ParserParseCString(Parser * p, const char * string); + +#endif /* end of include guard: PARSE_H_ONYWMADW */ diff --git a/tags b/tags new file mode 100644 index 00000000..b10d516d --- /dev/null +++ b/tags @@ -0,0 +1,425 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/ +!_TAG_PROGRAM_AUTHOR Universal Ctags Team // +!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/ +!_TAG_PROGRAM_URL https://ctags.io/ /official site/ +!_TAG_PROGRAM_VERSION 0.0.0 /d9090e1b/ +$(TARGET) Makefile /^$(TARGET): $(OBJECTS)$/;" t +%.o Makefile /^%.o : %.c$/;" t +ARRAY_H_BW48JZSK array.h /^#define ARRAY_H_BW48JZSK$/;" d +Array datatypes.h /^struct Array {$/;" s +Array datatypes.h /^typedef struct Array Array;$/;" t typeref:struct:Array +ArrayEnsure array.c /^void ArrayEnsure(GC * gc, Array * array, uint32_t capacity) {$/;" f typeref:typename:void +ArrayGet array.c /^Value ArrayGet(Array * array, uint32_t index) {$/;" f typeref:typename:Value +ArrayInit array.c /^void ArrayInit(GC * gc, Array * array, uint32_t capacity) {$/;" f typeref:typename:void +ArrayNew array.c /^Array * ArrayNew(GC * gc, uint32_t capacity) {$/;" f typeref:typename:Array * +ArrayPeek array.c /^Value ArrayPeek(Array * array) {$/;" f typeref:typename:Value +ArrayPop array.c /^Value ArrayPop(Array * array) {$/;" f typeref:typename:Value +ArrayPush array.c /^void ArrayPush(GC * gc, Array * array, Value x) {$/;" f typeref:typename:void +ArraySet array.c /^int ArraySet(Array * array, uint32_t index, Value x) {$/;" f typeref:typename:int +BUFFER_H_OEA9T4DJ buffer.h /^#define BUFFER_H_OEA9T4DJ$/;" d +Boolean datatypes.h /^typedef uint8_t Boolean;$/;" t typeref:typename:uint8_t +Buffer datatypes.h /^struct Buffer {$/;" s +Buffer datatypes.h /^typedef struct Buffer Buffer;$/;" t typeref:struct:Buffer +BufferAppendData buffer.c /^void BufferAppendData(GC * gc, Buffer * buffer, uint8_t * string, uint32_t length) {$/;" f typeref:typename:void +BufferDefine buffer.h /^#define BufferDefine(/;" d +BufferEnsure buffer.c /^void BufferEnsure(GC * gc, Buffer * buffer, uint32_t capacity) {$/;" f typeref:typename:void +BufferGet buffer.c /^int32_t BufferGet(Buffer * buffer, uint32_t index) {$/;" f typeref:typename:int32_t +BufferInit buffer.c /^void BufferInit(GC * gc, Buffer * buffer, uint32_t capacity) {$/;" f typeref:typename:void +BufferNew buffer.c /^Buffer * BufferNew(GC * gc, uint32_t capacity) {$/;" f typeref:typename:Buffer * +BufferPush buffer.c /^void BufferPush(GC * gc, Buffer * buffer, uint8_t c) {$/;" f typeref:typename:void +BufferToString buffer.c /^uint8_t * BufferToString(GC * gc, Buffer * buffer) {$/;" f typeref:typename:uint8_t * +CError compile.c /^#define CError(/;" d file: +CFLAGS Makefile /^CFLAGS=-std=c99 -Wall -Wextra -m32 -g$/;" m +CFunction datatypes.h /^typedef Value (*CFunction)(VM * vm);$/;" t typeref:typename:Value (*)(VM * vm) +COMPILE_H_9VXF71HY compile.h /^#define COMPILE_H_9VXF71HY$/;" d +CompileAddition compile.c /^static Slot CompileAddition(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileArray compile.c /^static Slot CompileArray(Compiler * c, FormOptions opts, Array * array) {$/;" f typeref:typename:Slot file: +CompileAssign compile.c /^static Slot CompileAssign(Compiler * c, FormOptions opts, Value left, Value right) {$/;" f typeref:typename:Slot file: +CompileBlock compile.c /^static Slot CompileBlock(Compiler * c, FormOptions opts, Array * form, uint32_t startIndex) {$/;" f typeref:typename:Slot file: +CompileDict compile.c /^static Slot CompileDict(Compiler * c, FormOptions opts, Dictionary * dict) {$/;" f typeref:typename:Slot file: +CompileDivision compile.c /^static Slot CompileDivision(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileDo compile.c /^static Slot CompileDo(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileForm compile.c /^static Slot CompileForm(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileFunction compile.c /^static Slot CompileFunction(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileIf compile.c /^static Slot CompileIf(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileLiteral compile.c /^static Slot CompileLiteral(Compiler * c, FormOptions opts, Value x) {$/;" f typeref:typename:Slot file: +CompileMultiplication compile.c /^static Slot CompileMultiplication(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileNonReferenceType compile.c /^static Slot CompileNonReferenceType(Compiler * c, FormOptions opts, Value x) {$/;" f typeref:typename:Slot file: +CompileOperator compile.c /^static Slot CompileOperator(Compiler * c, FormOptions opts, Array * form,$/;" f typeref:typename:Slot file: +CompileQuote compile.c /^static Slot CompileQuote(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileSet compile.c /^static Slot CompileSet(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileSubtraction compile.c /^static Slot CompileSubtraction(Compiler * c, FormOptions opts, Array * form) {$/;" f typeref:typename:Slot file: +CompileSymbol compile.c /^static Slot CompileSymbol(Compiler * c, FormOptions opts, Value sym) {$/;" f typeref:typename:Slot file: +CompileValue compile.c /^static Slot CompileValue(Compiler * c, FormOptions opts, Value x) {$/;" f typeref:typename:Slot file: +Compiler datatypes.h /^struct Compiler {$/;" s +Compiler datatypes.h /^typedef struct Compiler Compiler;$/;" t typeref:struct:Compiler +CompilerAddGlobal compile.c /^void CompilerAddGlobal(Compiler * c, const char * name, Value x) {$/;" f typeref:typename:void +CompilerAddLiteral compile.c /^static uint16_t CompilerAddLiteral(Compiler * c, Scope * scope, Value x) {$/;" f typeref:typename:uint16_t file: +CompilerCompile compile.c /^Func * CompilerCompile(Compiler * c, Value form) {$/;" f typeref:typename:Func * +CompilerDeclareSymbol compile.c /^static uint16_t CompilerDeclareSymbol(Compiler * c, Scope * scope, Value sym) {$/;" f typeref:typename:uint16_t file: +CompilerDropSlot compile.c /^static void CompilerDropSlot(Compiler * c, Scope * scope, Slot slot) {$/;" f typeref:typename:void file: +CompilerFreeLocal compile.c /^static void CompilerFreeLocal(Compiler * c, Scope * scope, uint16_t slot) {$/;" f typeref:typename:void file: +CompilerGC compile.c /^#define CompilerGC(/;" d file: +CompilerGenFuncDef compile.c /^static FuncDef * CompilerGenFuncDef(Compiler * c, uint32_t lastNBytes, uint32_t arity) {$/;" f typeref:typename:FuncDef * file: +CompilerGetLocal compile.c /^static uint16_t CompilerGetLocal(Compiler * c, Scope * scope) {$/;" f typeref:typename:uint16_t file: +CompilerInit compile.c /^void CompilerInit(Compiler * c, VM * vm) {$/;" f typeref:typename:void +CompilerPopScope compile.c /^static void CompilerPopScope(Compiler * c) {$/;" f typeref:typename:void file: +CompilerPushScope compile.c /^static Scope * CompilerPushScope(Compiler * c, int sameFunction) {$/;" f typeref:typename:Scope * file: +CompilerReturnSlot compile.c /^static void CompilerReturnSlot(Compiler * c, Slot slot) {$/;" f typeref:typename:void file: +CompilerTrackerFree compile.c /^static void CompilerTrackerFree(Compiler * c, Scope * scope, SlotTracker * tracker, int writeToB/;" f typeref:typename:void file: +CompilerTrackerInit compile.c /^static void CompilerTrackerInit(Compiler * c, SlotTracker * tracker) {$/;" f typeref:typename:void file: +CompilerTrackerPush compile.c /^static void CompilerTrackerPush(Compiler * c, SlotTracker * tracker, Slot slot) {$/;" f typeref:typename:void file: +DATATYPES_H_PJJ035NT datatypes.h /^#define DATATYPES_H_PJJ035NT$/;" d +DICT_H_YN2BKHUQ dict.h /^#define DICT_H_YN2BKHUQ$/;" d +DO_MULTI_MATH vm.c /^ #define DO_MULTI_MATH(/;" d file: +DictBucket datatypes.h /^struct DictBucket {$/;" s +DictBucket datatypes.h /^typedef struct DictBucket DictBucket;$/;" t typeref:struct:DictBucket +DictFind dict.c /^static DictBucket * DictFind(Dictionary * dict, Value * key, DictBucket ** prev) {$/;" f typeref:typename:DictBucket * file: +DictGet dict.c /^Value DictGet(Dictionary * dict, Value * key) {$/;" f typeref:typename:Value +DictInit dict.c /^void DictInit(GC * gc, Dictionary * dict, uint32_t capacity) {$/;" f typeref:typename:void +DictIterate dict.c /^void DictIterate(Dictionary * dict, DictionaryIterator * iterator) {$/;" f typeref:typename:void +DictIterateNext dict.c /^int DictIterateNext(DictionaryIterator * iterator, DictBucket ** bucket) {$/;" f typeref:typename:int +DictNew dict.c /^Dictionary * DictNew(GC * gc, uint32_t capacity) {$/;" f typeref:typename:Dictionary * +DictPut dict.c /^void DictPut(GC * gc, Dictionary * dict, Value * key, Value * value) {$/;" f typeref:typename:void +DictReHash dict.c /^static void DictReHash(GC * gc, Dictionary * dict, uint32_t size) {$/;" f typeref:typename:void file: +DictRemove dict.c /^Value DictRemove(GC * gc, Dictionary * dict, Value * key) {$/;" f typeref:typename:Value +Dictionary datatypes.h /^struct Dictionary {$/;" s +Dictionary datatypes.h /^typedef struct Dictionary Dictionary;$/;" t typeref:struct:Dictionary +DictionaryIterator datatypes.h /^struct DictionaryIterator {$/;" s +DictionaryIterator datatypes.h /^typedef struct DictionaryIterator DictionaryIterator;$/;" t typeref:struct:DictionaryIterator +DirectoryOfThisScript .ycm_extra_conf.py /^def DirectoryOfThisScript():$/;" f +EXPECTED_FUNCTION vm.c /^static const char EXPECTED_FUNCTION[] = "Expected function";$/;" v typeref:typename:const char[] file: +FRAME_SIZE datatypes.h /^#define FRAME_SIZE /;" d +FlagsForFile .ycm_extra_conf.py /^def FlagsForFile( filename, **kwargs ):$/;" f +FormOptions compile.c /^struct FormOptions {$/;" s file: +FormOptions compile.c /^typedef struct FormOptions FormOptions;$/;" t typeref:struct:FormOptions file: +FormOptionsDefault compile.c /^static FormOptions FormOptionsDefault() {$/;" f typeref:typename:FormOptions file: +FrameCallee datatypes.h /^#define FrameCallee(/;" d +FrameEnv datatypes.h /^#define FrameEnv(/;" d +FrameEnvValue datatypes.h /^#define FrameEnvValue(/;" d +FrameMeta datatypes.h /^#define FrameMeta(/;" d +FramePC datatypes.h /^#define FramePC(/;" d +FramePCValue datatypes.h /^#define FramePCValue(/;" d +FramePrevSize datatypes.h /^#define FramePrevSize(/;" d +FrameReturn datatypes.h /^#define FrameReturn(/;" d +FrameSize datatypes.h /^#define FrameSize(/;" d +Func datatypes.h /^struct Func {$/;" s +Func datatypes.h /^typedef struct Func Func;$/;" t typeref:struct:Func +FuncDef datatypes.h /^struct FuncDef {$/;" s +FuncDef datatypes.h /^typedef struct FuncDef FuncDef;$/;" t typeref:struct:FuncDef +FuncEnv datatypes.h /^struct FuncEnv {$/;" s +FuncEnv datatypes.h /^typedef struct FuncEnv FuncEnv;$/;" t typeref:struct:FuncEnv +GC datatypes.h /^struct GC {$/;" s +GC datatypes.h /^typedef struct GC GC;$/;" t typeref:struct:GC +GCAlloc gc.c /^void * GCAlloc(GC * gc, uint32_t size) {$/;" f typeref:typename:void * +GCClear gc.c /^void GCClear(GC * gc) {$/;" f typeref:typename:void +GCCollect gc.h /^#define GCCollect(/;" d +GCHeader gc.c /^#define GCHeader(/;" d file: +GCInit gc.c /^void GCInit(GC * gc, uint32_t memoryInterval) {$/;" f typeref:typename:void +GCMark gc.c /^void GCMark(GC * gc, Value * x) {$/;" f typeref:typename:void +GCMarkFuncEnv gc.c /^static void GCMarkFuncEnv(GC * gc, FuncEnv * env) {$/;" f typeref:typename:void file: +GCMarkMemory gc.c /^void GCMarkMemory(GC * gc, void * memory) {$/;" f typeref:typename:void +GCMaybeCollect gc.h /^#define GCMaybeCollect(/;" d +GCMemoryHeader gc.c /^struct GCMemoryHeader {$/;" s file: +GCMemoryHeader gc.c /^typedef struct GCMemoryHeader GCMemoryHeader;$/;" t typeref:struct:GCMemoryHeader file: +GCNeedsCollect gc.h /^#define GCNeedsCollect(/;" d +GCPrepare gc.c /^static void * GCPrepare(GC * gc, char * rawBlock, uint32_t size) {$/;" f typeref:typename:void * file: +GCSweep gc.c /^void GCSweep(GC * gc) {$/;" f typeref:typename:void +GCZalloc gc.c /^void * GCZalloc(GC * gc, uint32_t size) {$/;" f typeref:typename:void * +GC_H_N8L3U4KK gc.h /^#define GC_H_N8L3U4KK$/;" d +GetCompilationInfoForFile .ycm_extra_conf.py /^def GetCompilationInfoForFile( filename ):$/;" f +GetSpecial compile.c /^static SpecialFormHelper GetSpecial(Array * form) {$/;" f typeref:typename:SpecialFormHelper file: +GetUpValue vm.c /^static Value * GetUpValue(VM * vm, Func * fn, uint16_t level, uint16_t index) {$/;" f typeref:typename:Value * file: +HEX value.c /^#define HEX(/;" d file: +HEX_CHARACTERS value.c /^static const char * HEX_CHARACTERS = "0123456789ABCDEF";$/;" v typeref:typename:const char * file: +IsHeaderFile .ycm_extra_conf.py /^def IsHeaderFile( filename ):$/;" f +LoadCString value.c /^static uint8_t * LoadCString(GC * gc, const char * string, uint32_t len) {$/;" f typeref:typename:uint8_t * file: +LoadConstant vm.c /^static Value * LoadConstant(VM * vm, Func * fn, uint16_t index) {$/;" f typeref:typename:Value * file: +MakeRelativePathsInFlagsAbsolute .ycm_extra_conf.py /^def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):$/;" f +NO_UPVALUE vm.c /^static const char NO_UPVALUE[] = "Out of memory";$/;" v typeref:typename:const char[] file: +Number datatypes.h /^typedef double Number;$/;" t typeref:typename:double +NumberToString value.c /^static uint8_t * NumberToString(GC * gc, Number x) {$/;" f typeref:typename:uint8_t * file: +OBJECTS Makefile /^OBJECTS=$(patsubst %.c,%.o,$(SOURCES))$/;" m +OOM vm.c /^static const char OOM[] = "Out of memory";$/;" v typeref:typename:const char[] file: +OPCODES_H_EFPEYNZ0 opcodes.h /^#define OPCODES_H_EFPEYNZ0$/;" d +OpCode opcodes.h /^enum OpCode {$/;" g +PARSER_ERROR datatypes.h /^#define PARSER_ERROR /;" d +PARSER_FULL datatypes.h /^#define PARSER_FULL /;" d +PARSER_PENDING datatypes.h /^#define PARSER_PENDING /;" d +PARSE_ERROR parse.h /^#define PARSE_ERROR /;" d +PARSE_H_ONYWMADW parse.h /^#define PARSE_H_ONYWMADW$/;" d +PARSE_VALUE_PENDING parse.h /^#define PARSE_VALUE_PENDING /;" d +PARSE_VALUE_READ parse.h /^#define PARSE_VALUE_READ /;" d +PError parse.c /^#define PError(/;" d file: +PREFIX Makefile /^PREFIX=\/usr\/local$/;" m +PTYPE_ARRAY parse.c /^ PTYPE_ARRAY,$/;" e enum:ParseType file: +PTYPE_DICTIONARY parse.c /^ PTYPE_DICTIONARY,$/;" e enum:ParseType file: +PTYPE_FORM parse.c /^ PTYPE_FORM,$/;" e enum:ParseType file: +PTYPE_ROOT parse.c /^ PTYPE_ROOT,$/;" e enum:ParseType file: +PTYPE_STRING parse.c /^ PTYPE_STRING,$/;" e enum:ParseType file: +PTYPE_TOKEN parse.c /^ PTYPE_TOKEN$/;" e enum:ParseType file: +ParseReadNumber parse.c /^static int ParseReadNumber(const uint8_t * string, const uint8_t * end, double * ret, int forceI/;" f typeref:typename:int file: +ParseState datatypes.h /^typedef struct ParseState ParseState;$/;" t typeref:struct:ParseState +ParseState parse.c /^struct ParseState {$/;" s file: +ParseType parse.c /^typedef enum ParseType {$/;" g file: +ParseType parse.c /^} ParseType;$/;" t typeref:enum:ParseType file: +Parser datatypes.h /^struct Parser {$/;" s +Parser datatypes.h /^typedef struct Parser Parser;$/;" t typeref:struct:Parser +ParserArrayState parse.c /^static int ParserArrayState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserBuildTokenBuffer parse.c /^static Value ParserBuildTokenBuffer(Parser * p, Buffer * buf) {$/;" f typeref:typename:Value file: +ParserDictState parse.c /^static int ParserDictState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserDispatchChar parse.c /^static int ParserDispatchChar(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserFormState parse.c /^static int ParserFormState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserInit parse.c /^void ParserInit(Parser * p, VM * vm) {$/;" f typeref:typename:void +ParserMainState parse.c /^static int ParserMainState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserParseCString parse.c /^int ParserParseCString(Parser * p, const char * string) {$/;" f typeref:typename:int +ParserPeek parse.c /^static ParseState * ParserPeek(Parser * p) {$/;" f typeref:typename:ParseState * file: +ParserPop parse.c /^static ParseState * ParserPop(Parser * p) {$/;" f typeref:typename:ParseState * file: +ParserPush parse.c /^static void ParserPush(Parser *p, ParseType type) {$/;" f typeref:typename:void file: +ParserRootState parse.c /^static int ParserRootState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserStringState parse.c /^static int ParserStringState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserTokenState parse.c /^static int ParserTokenState(Parser * p, uint8_t c) {$/;" f typeref:typename:int file: +ParserTopAppend parse.c /^static void ParserTopAppend(Parser * p, Value x) {$/;" f typeref:typename:void file: +SOURCES Makefile /^SOURCES=main.c parse.c value.c vm.c dict.c array.c buffer.c gc.c compile.c$/;" m +SOURCE_EXTENSIONS .ycm_extra_conf.py /^SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]$/;" v +STRING_STATE_BASE parse.c /^ STRING_STATE_BASE,$/;" e enum:ParseState::__anon9bce43d1010a::__anon9bce43d10308::__anon9bce43d10403 file: +STRING_STATE_ESCAPE parse.c /^ STRING_STATE_ESCAPE,$/;" e enum:ParseState::__anon9bce43d1010a::__anon9bce43d10308::__anon9bce43d10403 file: +STRING_STATE_ESCAPE_HEX parse.c /^ STRING_STATE_ESCAPE_HEX$/;" e enum:ParseState::__anon9bce43d1010a::__anon9bce43d10308::__anon9bce43d10403 file: +STRING_STATE_ESCAPE_UNICODE parse.c /^ STRING_STATE_ESCAPE_UNICODE,$/;" e enum:ParseState::__anon9bce43d1010a::__anon9bce43d10308::__anon9bce43d10403 file: +Scope compile.c /^struct Scope {$/;" s file: +Scope datatypes.h /^typedef struct Scope Scope;$/;" t typeref:struct:Scope +ScopeSymbolResolve compile.c /^static int ScopeSymbolResolve(Scope * scope, Value x,$/;" f typeref:typename:int file: +Slot compile.c /^struct Slot {$/;" s file: +Slot compile.c /^typedef struct Slot Slot;$/;" t typeref:struct:Slot file: +SlotDefault compile.c /^static Slot SlotDefault() {$/;" f typeref:typename:Slot file: +SlotTracker compile.c /^struct SlotTracker {$/;" s file: +SlotTracker compile.c /^typedef struct SlotTracker SlotTracker;$/;" t typeref:struct:SlotTracker file: +SpecialFormHelper compile.c /^typedef Slot (*SpecialFormHelper) (Compiler * c, FormOptions opts, Array * form);$/;" t typeref:typename:Slot (*)(Compiler * c,FormOptions opts,Array * form) file: +StringDescription value.c /^static uint8_t * StringDescription(GC * gc, const char * title, uint32_t titlelen, void * pointe/;" f typeref:typename:uint8_t * file: +TARGET Makefile /^TARGET=interp$/;" m +THREAD_STATUS_ALIVE datatypes.h /^#define THREAD_STATUS_ALIVE /;" d +THREAD_STATUS_DEAD datatypes.h /^#define THREAD_STATUS_DEAD /;" d +THREAD_STATUS_PENDING datatypes.h /^#define THREAD_STATUS_PENDING /;" d +TYPE_ARRAY datatypes.h /^ TYPE_ARRAY,$/;" e enum:Type +TYPE_BOOLEAN datatypes.h /^ TYPE_BOOLEAN,$/;" e enum:Type +TYPE_BYTEBUFFER datatypes.h /^ TYPE_BYTEBUFFER,$/;" e enum:Type +TYPE_CFUNCTION datatypes.h /^ TYPE_CFUNCTION,$/;" e enum:Type +TYPE_DICTIONARY datatypes.h /^ TYPE_DICTIONARY,$/;" e enum:Type +TYPE_FORM datatypes.h /^ TYPE_FORM,$/;" e enum:Type +TYPE_FUNCDEF datatypes.h /^ TYPE_FUNCDEF,$/;" e enum:Type +TYPE_FUNCENV datatypes.h /^ TYPE_FUNCENV$/;" e enum:Type +TYPE_FUNCTION datatypes.h /^ TYPE_FUNCTION,$/;" e enum:Type +TYPE_NIL datatypes.h /^ TYPE_NIL = 0,$/;" e enum:Type +TYPE_NUMBER datatypes.h /^ TYPE_NUMBER,$/;" e enum:Type +TYPE_STRING datatypes.h /^ TYPE_STRING,$/;" e enum:Type +TYPE_SYMBOL datatypes.h /^ TYPE_SYMBOL,$/;" e enum:Type +TYPE_THREAD datatypes.h /^ TYPE_THREAD,$/;" e enum:Type +ThreadStack datatypes.h /^#define ThreadStack(/;" d +Type datatypes.h /^typedef enum Type {$/;" g +Type datatypes.h /^} Type;$/;" t typeref:enum:Type +UNEXPECTED_CLOSING_DELIM parse.c /^static const char UNEXPECTED_CLOSING_DELIM[] = "Unexpected closing delimiter";$/;" v typeref:typename:const char[] file: +VALUE_H_1RJPQKFM value.h /^#define VALUE_H_1RJPQKFM$/;" d +VM datatypes.h /^struct VM {$/;" s +VM datatypes.h /^typedef struct VM VM;$/;" t typeref:struct:VM +VMArg vm.c /^#define VMArg(/;" d file: +VMAssert vm.h /^#define VMAssert(/;" d +VMCallOp vm.c /^static void VMCallOp(VM * vm) {$/;" f typeref:typename:void file: +VMCollect vm.c /^void VMCollect(VM * vm) {$/;" f typeref:typename:void +VMDeinit vm.c /^void VMDeinit(VM * vm) {$/;" f typeref:typename:void +VMError vm.h /^#define VMError(/;" d +VMExit vm.h /^#define VMExit(/;" d +VMHandleOutOfMemory vm.c /^static void VMHandleOutOfMemory(GC * gc) {$/;" f typeref:typename:void file: +VMInit vm.c /^void VMInit(VM * vm) {$/;" f typeref:typename:void +VMLoad vm.c /^void VMLoad(VM * vm, Func * func) {$/;" f typeref:typename:void +VMMakeClosure vm.c /^static void VMMakeClosure(VM * vm, uint16_t ret, uint16_t literal) {$/;" f typeref:typename:void file: +VMMark vm.c /^void VMMark(VM * vm) {$/;" f typeref:typename:void +VMMaybeCollect vm.c /^void VMMaybeCollect(VM * vm) {$/;" f typeref:typename:void +VMOpArg vm.c /^#define VMOpArg(/;" d file: +VMPushCallee vm.c /^static void VMPushCallee(VM * vm, uint32_t ret, uint32_t arity, Value * callee) {$/;" f typeref:typename:void file: +VMResult vm.h /^#define VMResult(/;" d +VMReturn vm.c /^static void VMReturn(VM * vm, Value ret) {$/;" f typeref:typename:void file: +VMS_EXPECTED_NUMBER_LOP vm.c /^static const char VMS_EXPECTED_NUMBER_LOP[] = "Expected left operand to be number";$/;" v typeref:typename:const char[] file: +VMS_EXPECTED_NUMBER_ROP vm.c /^static const char VMS_EXPECTED_NUMBER_ROP[] = "Expected right operand to be number";$/;" v typeref:typename:const char[] file: +VMStart vm.c /^int VMStart(VM * vm) {$/;" f typeref:typename:int +VMTailCallOp vm.c /^static void VMTailCallOp(VM * vm) {$/;" f typeref:typename:void file: +VMThreadPop vm.c /^static void VMThreadPop(VM * vm, Array * thread) {$/;" f typeref:typename:void file: +VMThreadPush vm.c /^static void VMThreadPush(VM * vm, Array * thread, Value callee, uint32_t size) {$/;" f typeref:typename:void file: +VMThreadSplitStack vm.c /^static void VMThreadSplitStack(VM * vm, Array * thread) {$/;" f typeref:typename:void file: +VM_H_C4OZU8CQ vm.h /^#define VM_H_C4OZU8CQ$/;" d +VM_OP_ADD opcodes.h /^ VM_OP_ADD = 0, \/* 0x0000 *\/$/;" e enum:OpCode +VM_OP_ADM opcodes.h /^ VM_OP_ADM, \/* 0x001c *\/$/;" e enum:OpCode +VM_OP_ARR opcodes.h /^ VM_OP_ARR, \/* 0x0019 *\/$/;" e enum:OpCode +VM_OP_CAL opcodes.h /^ VM_OP_CAL, \/* 0x000e *\/$/;" e enum:OpCode +VM_OP_CLN opcodes.h /^ VM_OP_CLN, \/* 0x0015 *\/$/;" e enum:OpCode +VM_OP_CST opcodes.h /^ VM_OP_CST, \/* 0x0011 *\/$/;" e enum:OpCode +VM_OP_DIC opcodes.h /^ VM_OP_DIC, \/* 0x001a *\/$/;" e enum:OpCode +VM_OP_DIV opcodes.h /^ VM_OP_DIV, \/* 0x0003 *\/$/;" e enum:OpCode +VM_OP_DVM opcodes.h /^ VM_OP_DVM, \/* 0x001f *\/$/;" e enum:OpCode +VM_OP_EQL opcodes.h /^ VM_OP_EQL, \/* 0x0016 *\/$/;" e enum:OpCode +VM_OP_F64 opcodes.h /^ VM_OP_F64, \/* 0x0013 *\/$/;" e enum:OpCode +VM_OP_FLS opcodes.h /^ VM_OP_FLS, \/* 0x0007 *\/$/;" e enum:OpCode +VM_OP_I16 opcodes.h /^ VM_OP_I16, \/* 0x000a *\/$/;" e enum:OpCode +VM_OP_I32 opcodes.h /^ VM_OP_I32, \/* 0x0012 *\/$/;" e enum:OpCode +VM_OP_JIF opcodes.h /^ VM_OP_JIF, \/* 0x000c *\/$/;" e enum:OpCode +VM_OP_JMP opcodes.h /^ VM_OP_JMP, \/* 0x000d *\/$/;" e enum:OpCode +VM_OP_LD0 opcodes.h /^ VM_OP_LD0, \/* 0x0005 *\/$/;" e enum:OpCode +VM_OP_LD1 opcodes.h /^ VM_OP_LD1, \/* 0x0006 *\/$/;" e enum:OpCode +VM_OP_LTE opcodes.h /^ VM_OP_LTE, \/* 0x0018 *\/$/;" e enum:OpCode +VM_OP_LTN opcodes.h /^ VM_OP_LTN, \/* 0x0017 *\/$/;" e enum:OpCode +VM_OP_MOV opcodes.h /^ VM_OP_MOV, \/* 0x0014 *\/$/;" e enum:OpCode +VM_OP_MUL opcodes.h /^ VM_OP_MUL, \/* 0x0002 *\/$/;" e enum:OpCode +VM_OP_MUM opcodes.h /^ VM_OP_MUM, \/* 0x001e *\/$/;" e enum:OpCode +VM_OP_NIL opcodes.h /^ VM_OP_NIL, \/* 0x0009 *\/$/;" e enum:OpCode +VM_OP_NOT opcodes.h /^ VM_OP_NOT, \/* 0x0004 *\/$/;" e enum:OpCode +VM_OP_RET opcodes.h /^ VM_OP_RET, \/* 0x000f *\/$/;" e enum:OpCode +VM_OP_RTN opcodes.h /^ VM_OP_RTN \/* 0x0020 *\/$/;" e enum:OpCode +VM_OP_SBM opcodes.h /^ VM_OP_SBM, \/* 0x001d *\/$/;" e enum:OpCode +VM_OP_SUB opcodes.h /^ VM_OP_SUB, \/* 0x0001 *\/$/;" e enum:OpCode +VM_OP_SUV opcodes.h /^ VM_OP_SUV, \/* 0x0010 *\/$/;" e enum:OpCode +VM_OP_TCL opcodes.h /^ VM_OP_TCL, \/* 0x001b *\/$/;" e enum:OpCode +VM_OP_TRU opcodes.h /^ VM_OP_TRU, \/* 0x0008 *\/$/;" e enum:OpCode +VM_OP_UPV opcodes.h /^ VM_OP_UPV, \/* 0x000b *\/$/;" e enum:OpCode +VSTRING_H_TFCSVBEE vstring.h /^#define VSTRING_H_TFCSVBEE$/;" d +VStringHash vstring.h /^#define VStringHash(/;" d +VStringRaw vstring.h /^#define VStringRaw(/;" d +VStringSize vstring.h /^#define VStringSize(/;" d +Value datatypes.h /^struct Value {$/;" s +Value datatypes.h /^typedef struct Value Value;$/;" t typeref:struct:Value +ValueCompare value.c /^int ValueCompare(Value * x, Value * y) {$/;" f typeref:typename:int +ValueData datatypes.h /^typedef union ValueData ValueData;$/;" t typeref:union:ValueData +ValueData datatypes.h /^union ValueData {$/;" u +ValueEqual value.c /^int ValueEqual(Value * x, Value * y) {$/;" f typeref:typename:int +ValueHash value.c /^uint32_t ValueHash(Value * x) {$/;" f typeref:typename:uint32_t +ValueLoadCString value.c /^Value ValueLoadCString(GC * gc, const char * string) {$/;" f typeref:typename:Value +ValuePrint value.c /^void ValuePrint(Value * x, uint32_t indent) {$/;" f typeref:typename:void +ValueToString value.c /^uint8_t * ValueToString(GC * gc, Value * x) {$/;" f typeref:typename:uint8_t * +__anon69403613010a value.c /^ union {$/;" u function:StringDescription file: +__anon69403613020a value.c /^ union {$/;" u function:ValueHash file: +__anon9bce43d1010a parse.c /^ union {$/;" u struct:ParseState file: +__anon9bce43d10208 parse.c /^ struct {$/;" s union:ParseState::__anon9bce43d1010a file: +__anon9bce43d10308 parse.c /^ struct {$/;" s union:ParseState::__anon9bce43d1010a file: +__anon9bce43d10403 parse.c /^ enum {$/;" g struct:ParseState::__anon9bce43d1010a::__anon9bce43d10308 file: +all Makefile /^all: $(TARGET)$/;" t +arity datatypes.h /^ uint32_t arity;$/;" m struct:FuncDef typeref:typename:uint32_t +array datatypes.h /^ Array * array;$/;" m union:ValueData typeref:typename:Array * +array parse.c /^ Array * array;$/;" m union:ParseState::__anon9bce43d1010a typeref:typename:Array * file: +base datatypes.h /^ Value * base;$/;" m struct:VM typeref:typename:Value * +black datatypes.h /^ uint32_t black : 1;$/;" m struct:GC typeref:typename:uint32_t:1 +blocks datatypes.h /^ void * blocks;$/;" m struct:GC typeref:typename:void * +boolean datatypes.h /^ Boolean boolean;$/;" m union:ValueData typeref:typename:Boolean +bucket datatypes.h /^ DictBucket * bucket;$/;" m struct:DictionaryIterator typeref:typename:DictBucket * +buckets datatypes.h /^ DictBucket ** buckets;$/;" m struct:Dictionary typeref:typename:DictBucket ** +buf parse.c /^ } buf;$/;" m struct:ParseState typeref:union:ParseState::__anon9bce43d1010a file: +buffer datatypes.h /^ Buffer * buffer;$/;" m union:ValueData typeref:typename:Buffer * +buffer datatypes.h /^ Buffer buffer;$/;" m struct:Compiler typeref:typename:Buffer +buffer parse.c /^ Buffer * buffer;$/;" m struct:ParseState::__anon9bce43d1010a::__anon9bce43d10308 typeref:typename:Buffer * file: +byteCode datatypes.h /^ uint16_t * byteCode;$/;" m struct:FuncDef typeref:typename:uint16_t * +byteCodeLen datatypes.h /^ uint32_t byteCodeLen;$/;" m struct:FuncDef typeref:typename:uint32_t +bytes value.c /^ uint8_t bytes[sizeof(void *)];$/;" m union:StringDescription::__anon69403613010a typeref:typename:uint8_t[] file: +canChoose compile.c /^ uint16_t canChoose : 1;$/;" m struct:FormOptions typeref:typename:uint16_t:1 file: +canDrop compile.c /^ uint16_t canDrop : 1;$/;" m struct:FormOptions typeref:typename:uint16_t:1 file: +cap datatypes.h /^ uint32_t cap;$/;" m struct:Parser typeref:typename:uint32_t +capacity compile.c /^ uint32_t capacity;$/;" m struct:SlotTracker typeref:typename:uint32_t file: +capacity datatypes.h /^ uint32_t capacity;$/;" m struct:Array typeref:typename:uint32_t +capacity datatypes.h /^ uint32_t capacity;$/;" m struct:Buffer typeref:typename:uint32_t +capacity datatypes.h /^ uint32_t capacity;$/;" m struct:Dictionary typeref:typename:uint32_t +cfunction datatypes.h /^ CFunction cfunction;$/;" m union:ValueData typeref:typename:CFunction +checkStrConst parse.c /^static int checkStrConst(const char * ref, const uint8_t * start, const uint8_t * end) {$/;" f typeref:typename:int file: +clean Makefile /^clean:$/;" t +color gc.c /^ uint32_t color;$/;" m struct:GCMemoryHeader typeref:typename:uint32_t file: +compilation_database_folder .ycm_extra_conf.py /^compilation_database_folder = ''$/;" v +count compile.c /^ uint32_t count;$/;" m struct:SlotTracker typeref:typename:uint32_t file: +count datatypes.h /^ uint32_t count;$/;" m struct:Array typeref:typename:uint32_t +count datatypes.h /^ uint32_t count;$/;" m struct:Buffer typeref:typename:uint32_t +count datatypes.h /^ uint32_t count;$/;" m struct:Dictionary typeref:typename:uint32_t +count datatypes.h /^ uint32_t count;$/;" m struct:Parser typeref:typename:uint32_t +data datatypes.h /^ ParseState * data;$/;" m struct:Parser typeref:typename:ParseState * +data datatypes.h /^ Value * data;$/;" m struct:Array typeref:typename:Value * +data datatypes.h /^ ValueData data;$/;" m struct:Value typeref:typename:ValueData +data datatypes.h /^ uint8_t * data;$/;" m struct:Buffer typeref:typename:uint8_t * +data datatypes.h /^} data;$/;" v typeref:union:ValueData +database .ycm_extra_conf.py /^ database = ycm_core.CompilationDatabase( compilation_database_folder )$/;" v +debug Makefile /^debug: $(TARGET)$/;" t +debugRepl main.c /^void debugRepl() {$/;" f typeref:typename:void +def datatypes.h /^ FuncDef * def;$/;" m struct:Func typeref:typename:FuncDef * +dict datatypes.h /^ Dictionary * dict;$/;" m struct:DictionaryIterator typeref:typename:Dictionary * +dict datatypes.h /^ Dictionary * dict;$/;" m union:ValueData typeref:typename:Dictionary * +dict parse.c /^ Dictionary * dict;$/;" m struct:ParseState::__anon9bce43d1010a::__anon9bce43d10208 typeref:typename:Dictionary * file: +dictState parse.c /^ } dictState;$/;" m union:ParseState::__anon9bce43d1010a typeref:struct:ParseState::__anon9bce43d1010a::__anon9bce43d10208 file: +djb2 value.c /^uint32_t djb2(const uint8_t * str) {$/;" f typeref:typename:uint32_t +env datatypes.h /^ Array env;$/;" m struct:Compiler typeref:typename:Array +env datatypes.h /^ FuncEnv * env;$/;" m struct:Func typeref:typename:FuncEnv * +error datatypes.h /^ const char * error;$/;" m struct:Compiler typeref:typename:const char * +error datatypes.h /^ const char * error;$/;" m struct:Parser typeref:typename:const char * +error datatypes.h /^ const char * error;$/;" m struct:VM typeref:typename:const char * +exp10 parse.c /^static double exp10(int power) {$/;" f typeref:typename:double file: +flags .ycm_extra_conf.py /^flags = [$/;" v +freeHeap compile.c /^ uint16_t * freeHeap;$/;" m struct:Scope typeref:typename:uint16_t * file: +func datatypes.h /^ Func * func;$/;" m union:ValueData typeref:typename:Func * +funcdef datatypes.h /^ FuncDef * funcdef;$/;" m union:ValueData typeref:typename:FuncDef * +funcenv datatypes.h /^ FuncEnv * funcenv;$/;" m union:ValueData typeref:typename:FuncEnv * +gc datatypes.h /^ GC gc;$/;" m struct:VM typeref:typename:GC +handleOutOfMemory datatypes.h /^ void (*handleOutOfMemory)(GC * gc);$/;" m struct:GC typeref:typename:void (*)(GC * gc) +hash value.c /^ uint32_t hash;$/;" m union:ValueHash::__anon69403613020a typeref:typename:uint32_t file: +heapCapacity compile.c /^ uint32_t heapCapacity;$/;" m struct:Scope typeref:typename:uint32_t file: +heapSize compile.c /^ uint32_t heapSize;$/;" m struct:Scope typeref:typename:uint32_t file: +index compile.c /^ uint16_t index;$/;" m struct:Slot typeref:typename:uint16_t file: +index datatypes.h /^ uint32_t index;$/;" m struct:DictionaryIterator typeref:typename:uint32_t +index datatypes.h /^ uint32_t index;$/;" m struct:Parser typeref:typename:uint32_t +install Makefile /^install: $(TARGET)$/;" t +isDud compile.c /^ uint16_t isDud : 1;$/;" m struct:Slot typeref:typename:uint16_t:1 file: +isSymbolChar parse.c /^static int isSymbolChar(uint8_t c) {$/;" f typeref:typename:int file: +isTail compile.c /^ uint16_t isTail : 1;$/;" m struct:FormOptions typeref:typename:uint16_t:1 file: +isTemp compile.c /^ uint16_t isTemp : 1;$/;" m struct:Slot typeref:typename:uint16_t:1 file: +isWhitespace parse.c /^static int isWhitespace(uint8_t c) {$/;" f typeref:typename:int file: +jump datatypes.h /^ jmp_buf jump;$/;" m struct:VM typeref:typename:jmp_buf +key datatypes.h /^ Value key;$/;" m struct:DictBucket typeref:typename:Value +key parse.c /^ Value key;$/;" m struct:ParseState::__anon9bce43d1010a::__anon9bce43d10208 typeref:typename:Value file: +keyFound parse.c /^ int keyFound;$/;" m struct:ParseState::__anon9bce43d1010a::__anon9bce43d10208 typeref:typename:int file: +literals compile.c /^ Dictionary * literals;$/;" m struct:Scope typeref:typename:Dictionary * file: +literals datatypes.h /^ Value * literals; \/* Contains strings, FuncDefs, etc. *\/$/;" m struct:FuncDef typeref:typename:Value * +literalsArray compile.c /^ Array * literalsArray;$/;" m struct:Scope typeref:typename:Array * file: +literalsLen datatypes.h /^ uint32_t literalsLen;$/;" m struct:FuncDef typeref:typename:uint32_t +locals compile.c /^ Dictionary locals;$/;" m struct:Scope typeref:typename:Dictionary file: +locals datatypes.h /^ uint32_t locals;$/;" m struct:FuncDef typeref:typename:uint32_t +main main.c /^int main() {$/;" f typeref:typename:int +memoryInterval datatypes.h /^ uint32_t memoryInterval;$/;" m struct:GC typeref:typename:uint32_t +next datatypes.h /^ DictBucket * next;$/;" m struct:DictBucket typeref:typename:DictBucket * +next gc.c /^ GCMemoryHeader * next;$/;" m struct:GCMemoryHeader typeref:typename:GCMemoryHeader * file: +nextCollection datatypes.h /^ uint32_t nextCollection;$/;" m struct:GC typeref:typename:uint32_t +nextLocal compile.c /^ uint16_t nextLocal;$/;" m struct:Scope typeref:typename:uint16_t file: +nextScope compile.c /^ Scope * nextScope;$/;" m struct:Scope typeref:typename:Scope * file: +number datatypes.h /^ Number number;$/;" m union:ValueData typeref:typename:Number +number value.c /^ Number number;$/;" m union:ValueHash::__anon69403613020a typeref:typename:Number file: +onError datatypes.h /^ jmp_buf onError;$/;" m struct:Compiler typeref:typename:jmp_buf +p value.c /^ void * p;$/;" m union:StringDescription::__anon69403613010a typeref:typename:void * file: +parent datatypes.h /^ Func * parent;$/;" m struct:Func typeref:typename:Func * +pc datatypes.h /^ uint16_t * pc;$/;" m struct:VM typeref:typename:uint16_t * +pointer datatypes.h /^ void * pointer;$/;" m union:ValueData typeref:typename:void * +previousScope compile.c /^ Scope * previousScope;$/;" m struct:Scope typeref:typename:Scope * file: +root datatypes.h /^ Scope * root;$/;" m struct:Compiler typeref:typename:Scope * +run Makefile /^run: $(TARGET)$/;" t +slots compile.c /^ Slot * slots;$/;" m struct:SlotTracker typeref:typename:Slot * file: +stackOffset datatypes.h /^ uint32_t stackOffset; \/* Used as environment size when off stack *\/$/;" m struct:FuncEnv typeref:typename:uint32_t +state parse.c /^ } state;$/;" m struct:ParseState::__anon9bce43d1010a::__anon9bce43d10308 typeref:enum:ParseState::__anon9bce43d1010a::__anon9bce43d10308::__anon9bce43d10403 file: +status datatypes.h /^ uint32_t status;$/;" m struct:Parser typeref:typename:uint32_t +string datatypes.h /^ uint8_t * string;$/;" m union:ValueData typeref:typename:uint8_t * +string parse.c /^ } string;$/;" m union:ParseState::__anon9bce43d1010a typeref:struct:ParseState::__anon9bce43d1010a::__anon9bce43d10308 file: +tail datatypes.h /^ Scope * tail;$/;" m struct:Compiler typeref:typename:Scope * +target compile.c /^ uint16_t target;$/;" m struct:FormOptions typeref:typename:uint16_t file: +tempRoot datatypes.h /^ Value tempRoot; \/* Temporary GC root *\/$/;" m struct:VM typeref:typename:Value +thread datatypes.h /^ Array * thread; \/* When nil, index the local values *\/$/;" m struct:FuncEnv typeref:typename:Array * +thread datatypes.h /^ Array * thread;$/;" m struct:VM typeref:typename:Array * +truthy vm.c /^static int truthy(Value * v) {$/;" f typeref:typename:int file: +type datatypes.h /^ Type type;$/;" m struct:Value typeref:typename:Type +type parse.c /^ ParseType type;$/;" m struct:ParseState typeref:typename:ParseType file: +u16 datatypes.h /^ uint16_t u16[4];$/;" m union:ValueData typeref:typename:uint16_t[4] +u8 datatypes.h /^ uint8_t u8[8];$/;" m union:ValueData typeref:typename:uint8_t[8] +user datatypes.h /^ void * user;$/;" m struct:GC typeref:typename:void * +value datatypes.h /^ Value value;$/;" m struct:DictBucket typeref:typename:Value +value datatypes.h /^ Value value;$/;" m struct:Parser typeref:typename:Value +values datatypes.h /^ Value * values;$/;" m struct:FuncEnv typeref:typename:Value * +vm datatypes.h /^ VM * vm;$/;" m struct:Compiler typeref:typename:VM * +vm datatypes.h /^ VM * vm;$/;" m struct:Parser typeref:typename:VM * diff --git a/value.c b/value.c new file mode 100644 index 00000000..3df088c1 --- /dev/null +++ b/value.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include "gc.h" +#include "vstring.h" +#include "value.h" +#include "buffer.h" + +/* Print a value recursively. Used for debugging */ +void ValuePrint(Value * x, uint32_t indent) { + uint32_t i; + for (i = 0; i < indent; ++i) + fputc(' ', stdout); + switch (x->type) { + case TYPE_NIL: + printf(""); + break; + case TYPE_BOOLEAN: + printf(x->data.boolean ? "" : ""); + break; + case TYPE_NUMBER: + printf("%f", x->data.number); + break; + case TYPE_FORM: + case TYPE_ARRAY: + if (x->type == TYPE_ARRAY) printf(" [\n"); else printf(" (\n"); + for (i = 0; i < x->data.array->count; ++i) { + ValuePrint(x->data.array->data + i, indent + 4); + printf("\n"); + } + for (i = 0; i < indent; ++i) fputc(' ', stdout); + if (x->type == TYPE_ARRAY) printf(" ]\n"); else printf(" )\n"); + break; + case TYPE_STRING: + printf("\"%.*s\"", VStringSize(x->data.string), (char *) x->data.string); + break; + case TYPE_SYMBOL: + printf("%.*s", VStringSize(x->data.string), (char *) x->data.string); + break; + case TYPE_CFUNCTION: + printf(""); + break; + case TYPE_FUNCTION: + printf(""); + break; + case TYPE_DICTIONARY: + printf(""); + break; + case TYPE_BYTEBUFFER: + printf(""); + break; + case TYPE_FUNCDEF: + printf(""); + break; + case TYPE_FUNCENV: + printf(""); + break; + case TYPE_THREAD: + printf(""); + break; + } +} + +static uint8_t * LoadCString(GC * gc, const char * string, uint32_t len) { + uint8_t * data = GCAlloc(gc, len + 2 * sizeof(uint32_t)); + data += 2 * sizeof(uint32_t); + VStringHash(data) = 0; + VStringSize(data) = len; + memcpy(data, string, len); + return data; +} + +Value ValueLoadCString(GC * gc, const char * string) { + Value ret; + ret.type = TYPE_STRING; + ret.data.string = LoadCString(gc, string, strlen(string)); + return ret; +} + +static uint8_t * NumberToString(GC * gc, Number x) { + static const uint32_t SIZE = 20; + uint8_t * data = GCAlloc(gc, SIZE + 2 * sizeof(uint32_t)); + data += 2 * sizeof(uint32_t); + snprintf((char *) data, SIZE, "%.17g", x); + VStringHash(data) = 0; + VStringSize(data) = strlen((char *) data); + return data; +} + +static const char * HEX_CHARACTERS = "0123456789ABCDEF"; +#define HEX(i) (((uint8_t *) HEX_CHARACTERS)[(i)]) + +/* Returns a string description for a pointer */ +static uint8_t * StringDescription(GC * gc, const char * title, uint32_t titlelen, void * pointer) { + uint32_t len = 3 + titlelen + sizeof(pointer) * 2; + uint32_t i; + uint8_t * data = GCAlloc(gc, len + 2 * sizeof(uint32_t)); + uint8_t * c; + union { + uint8_t bytes[sizeof(void *)]; + void * p; + } buf; + buf.p = pointer; + data += 2 * sizeof(uint32_t); + c = data; + *c++ = '<'; + for (i = 0; i < titlelen; ++i) { + *c++ = ((uint8_t *)title) [i]; + } + *c++ = ' '; + for (i = 0; i < sizeof(void *); ++i) { + uint8_t byte = buf.bytes[i]; + *c++ = HEX(byte >> 4); + *c++ = HEX(byte & 0xF); + } + *c++ = '>'; + return data; +} + +/* Returns a string pointer or NULL if could not allocate memory. */ +uint8_t * ValueToString(GC * gc, Value * x) { + switch (x->type) { + case TYPE_NIL: + return LoadCString(gc, "nil", 3); + case TYPE_BOOLEAN: + if (x->data.boolean) { + return LoadCString(gc, "true", 4); + } else { + return LoadCString(gc, "false", 5); + } + case TYPE_NUMBER: + return NumberToString(gc, x->data.number); + case TYPE_ARRAY: + return StringDescription(gc, "array", 5, x->data.array); + case TYPE_FORM: + return StringDescription(gc, "form", 4, x->data.array); + case TYPE_STRING: + case TYPE_SYMBOL: + return x->data.string; + case TYPE_BYTEBUFFER: + return StringDescription(gc, "buffer", 6, x->data.buffer); + case TYPE_CFUNCTION: + return StringDescription(gc, "cfunction", 9, x->data.cfunction); + case TYPE_FUNCTION: + return StringDescription(gc, "function", 8, x->data.func); + case TYPE_DICTIONARY: + return StringDescription(gc, "dictionary", 10, x->data.dict); + case TYPE_FUNCDEF: + return StringDescription(gc, "funcdef", 7, x->data.funcdef); + case TYPE_FUNCENV: + return StringDescription(gc, "funcenv", 7, x->data.funcenv); + case TYPE_THREAD: + return StringDescription(gc, "thread", 6, x->data.array); + } + return NULL; +} + +/* Simple hash function */ +uint32_t djb2(const uint8_t * str) { + const uint8_t * end = str + VStringSize(str); + uint32_t hash = 5381; + while (str < end) + hash = (hash << 5) + hash + *str++; + return hash; +} + +/* Check if two values are equal. This is strict equality with no conversion. */ +int ValueEqual(Value * x, Value * y) { + int result; + if (x->type != y->type) { + result = 0; + } else { + switch (x->type) { + case TYPE_NIL: + result = 1; + break; + case TYPE_BOOLEAN: + result = x->data.boolean == y->data.boolean; + break; + case TYPE_NUMBER: + result = x->data.number == y->data.number; + break; + /* Assume that when strings are created, equal strings + * are set to the same string */ + case TYPE_STRING: + case TYPE_SYMBOL: + if (x->data.string == y->data.string) { + result = 1; + break; + } + if (ValueHash(x) != ValueHash(y) || + VStringSize(x->data.string) != VStringSize(y->data.string)) { + result = 0; + break; + } + /* If two different strings are equal, merge them to share the same data */ + if (!strncmp((char *) x->data.string, (char *) y->data.string, VStringSize(x->data.string))) { + /* Use the lower pointer in memory. This means that in long running + * programs, repeated string compares will eventually all use identical + * pointers for identical strings. */ + if (x->data.string < y->data.string) { + y->data.string = x->data.string; + } else { + x->data.string = y->data.string; + } + result = 1; + break; + } + result = 0; + break; + case TYPE_ARRAY: + case TYPE_FORM: + case TYPE_BYTEBUFFER: + case TYPE_CFUNCTION: + case TYPE_DICTIONARY: + case TYPE_FUNCTION: + case TYPE_FUNCDEF: + case TYPE_FUNCENV: + case TYPE_THREAD: + /* compare pointers */ + result = x->data.array == y->data.array; + break; + } + } + return result; +} + +/* Computes a hash value for a function */ +uint32_t ValueHash(Value * x) { + uint32_t hash; + switch (x->type) { + case TYPE_NIL: + hash = 0; + break; + case TYPE_BOOLEAN: + hash = x->data.boolean; + break; + case TYPE_NUMBER: + { + union { + uint32_t hash; + Number number; + } u; + u.number = x->data.number; + hash = u.hash; + } + break; + /* String hashes */ + case TYPE_SYMBOL: + case TYPE_STRING: + /* Assume 0 is not hashed. */ + if (VStringHash(x->data.string)) + hash = VStringHash(x->data.string); + else + hash = VStringHash(x->data.string) = djb2(x->data.string); + break; + case TYPE_ARRAY: + case TYPE_FORM: + case TYPE_BYTEBUFFER: + case TYPE_CFUNCTION: + case TYPE_DICTIONARY: + case TYPE_FUNCTION: + case TYPE_FUNCDEF: + case TYPE_FUNCENV: + case TYPE_THREAD: + /* Cast the pointer */ + hash = (uint32_t) x->data.string; + break; + } + return hash; +} + +/* Compares x to y. If they are equal retuns 0. If x is less, returns -1. + * If y is less, returns 1. All types are comparable + * and should have strict ordering. */ +int ValueCompare(Value * x, Value * y) { + if (x->type == y->type) { + switch (x->type) { + case TYPE_NIL: + return 0; + case TYPE_BOOLEAN: + if (x->data.boolean == y->data.boolean) { + return 0; + } else { + return x->data.boolean ? 1 : -1; + } + case TYPE_NUMBER: + /* TODO: define behavior for NaN and infinties. */ + if (x->data.number == y->data.number) { + return 0; + } else { + return x->data.number > y->data.number ? 1 : -1; + } + case TYPE_STRING: + case TYPE_SYMBOL: + if (x->data.string == y->data.string) { + return 0; + } else { + uint32_t xlen = VStringSize(x->data.string); + uint32_t ylen = VStringSize(y->data.string); + uint32_t len = xlen > ylen ? ylen : xlen; + uint32_t i; + for (i = 0; i < len; ++i) { + if (x->data.string[i] == y->data.string[i]) { + continue; + } else if (x->data.string[i] < y->data.string[i]) { + return 1; /* x is less then y */ + } else { + return -1; /* y is less than x */ + } + } + if (xlen == ylen) { + /* Merge the two strings */ + if (x->data.string < y->data.string) { + y->data.string = x->data.string; + } else { + x->data.string = y->data.string; + } + return 0; + } else { + return xlen < ylen ? -1 : 1; + } + } + case TYPE_ARRAY: + case TYPE_FORM: + case TYPE_BYTEBUFFER: + case TYPE_CFUNCTION: + case TYPE_FUNCTION: + case TYPE_DICTIONARY: + case TYPE_FUNCDEF: + case TYPE_FUNCENV: + case TYPE_THREAD: + if (x->data.string == y->data.string) { + return 0; + } else { + return x->data.string > y->data.string ? 1 : -1; + } + } + } else if (x->type < y->type) { + return -1; + } + return 1; +} diff --git a/value.h b/value.h new file mode 100644 index 00000000..ed1824e3 --- /dev/null +++ b/value.h @@ -0,0 +1,18 @@ +#ifndef VALUE_H_1RJPQKFM +#define VALUE_H_1RJPQKFM + +#include "datatypes.h" + +void ValuePrint(Value * x, uint32_t indent); + +int ValueCompare(Value * x, Value * y); + +int ValueEqual(Value * x, Value * y); + +Value ValueLoadCString(GC * gc, const char * string); + +uint8_t * ValueToString(GC * gc, Value * x); + +uint32_t ValueHash(Value * x); + +#endif /* end of include guard: VALUE_H_1RJPQKFM */ diff --git a/vm.c b/vm.c new file mode 100644 index 00000000..860e866e --- /dev/null +++ b/vm.c @@ -0,0 +1,605 @@ +#include +#include +#include +#include "vm.h" +#include "value.h" +#include "array.h" +#include "vstring.h" +#include "dict.h" +#include "gc.h" +#include "buffer.h" +#include "opcodes.h" + +#define VMArg(i) (vm->base + (i)) +#define VMOpArg(i) (VMArg(vm->pc[(i)])) + +static const char OOM[] = "Out of memory"; +static const char NO_UPVALUE[] = "Out of memory"; +static const char EXPECTED_FUNCTION[] = "Expected function"; +static const char VMS_EXPECTED_NUMBER_ROP[] = "Expected right operand to be number"; +static const char VMS_EXPECTED_NUMBER_LOP[] = "Expected left operand to be number"; + +/* Mark memory reachable by VM */ +void VMMark(VM * vm) { + Value thread; + thread.type = TYPE_THREAD; + thread.data.array = vm->thread; + GCMark(&vm->gc, &thread); + GCMark(&vm->gc, &vm->tempRoot); +} + +/* Run garbage collection */ +void VMCollect(VM * vm) { + VMMark(vm); + GCSweep(&vm->gc); +} + +/* Run garbage collection if needed */ +void VMMaybeCollect(VM * vm) { + if (GCNeedsCollect(&vm->gc)) { + VMCollect(vm); + } +} + +/* OOM handler for the vm's gc */ +static void VMHandleOutOfMemory(GC * gc) { + VM * vm = (VM *) gc->user; + VMError(vm, OOM); +} + +/* Push a stack frame onto a thread */ +static void VMThreadPush(VM * vm, Array * thread, Value callee, uint32_t size) { + uint16_t oldSize; + uint32_t nextCount, i; + if (thread->count) { + oldSize = FrameSize(thread); + nextCount = thread->count + oldSize + FRAME_SIZE; + } else { + oldSize = 0; + nextCount = FRAME_SIZE; + } + ArrayEnsure(&vm->gc, thread, nextCount + size); + /* Ensure values start out as nil so as to not confuse + * the garabage collector */ + for (i = nextCount; i < nextCount + size; ++i) { + thread->data[i].type = TYPE_NIL; + } + thread->count = nextCount; + FramePrevSize(thread) = oldSize; + FrameSize(thread) = size; + FrameEnvValue(thread).type = TYPE_NIL; + FrameEnv(thread) = NULL; + FrameCallee(thread) = callee; + FrameMeta(thread).type = TYPE_NUMBER; + FramePCValue(thread).type = TYPE_NUMBER; + vm->base = ThreadStack(thread); +} + +/* Copy the current function stack to the current closure + environment */ +static void VMThreadSplitStack(VM * vm, Array * thread) { + FuncEnv * env = FrameEnv(thread); + /* Check for closures */ + if (env) { + uint32_t size = FrameSize(thread); + env->thread = NULL; + env->stackOffset = size; + env->values = GCAlloc(&vm->gc, sizeof(Value) * size); + memcpy(env->values, ThreadStack(thread), size * sizeof(Value)); + } +} + +/* Pop the top-most stack frame from stack */ +static void VMThreadPop(VM * vm, Array * thread) { + if (thread->count) { + VMThreadSplitStack(vm, thread); + thread->count -= FRAME_SIZE + FramePrevSize(thread); + } else { + VMError(vm, "Nothing to pop from stack."); + } + vm->base = ThreadStack(thread); +} + +/* Get an upvalue */ +static Value * GetUpValue(VM * vm, Func * fn, uint16_t level, uint16_t index) { + FuncEnv * env; + Value * stack; + while (fn && level--) + fn = fn->parent; + VMAssert(vm, fn, NO_UPVALUE); + env = fn->env; + if (env->thread) + stack = env->thread->data + env->stackOffset; + else + stack = env->values; + return stack + index; +} + +/* Get a constant */ +static Value * LoadConstant(VM * vm, Func * fn, uint16_t index) { + if (index > fn->def->literalsLen) { + VMError(vm, NO_UPVALUE); + } + return fn->def->literals + index; +} + +/* Truthiness definition in VM */ +static int truthy(Value * v) { + return v->type != TYPE_NIL && !(v->type == TYPE_BOOLEAN && !v->data.boolean); +} + +/* Pushes a function on the call stack. */ +static void VMPushCallee(VM * vm, uint32_t ret, uint32_t arity, Value callee) { + Array * thread = vm->thread; + FrameReturn(thread) = ret; + if (callee.type == TYPE_FUNCTION) { + Func * fn = callee.data.func; + VMThreadPush(vm, thread, callee, fn->def->locals); + } else if (callee.type == TYPE_CFUNCTION) { + VMThreadPush(vm, thread, callee, arity); + } else { + VMError(vm, EXPECTED_FUNCTION); + return; + } + /* Reset the base and frame after changing the stack */ + vm->base = ThreadStack(thread); +} + +/* Return from the vm */ +static void VMReturn(VM * vm, Value ret) { + VMThreadPop(vm, vm->thread); + if (vm->thread->count == 0) { + VMExit(vm, ret); + } + vm->base = ThreadStack(vm->thread); + vm->pc = FramePC(vm->thread); + vm->base[FrameReturn(vm->thread)] = ret; +} + +/* Implementation of the opcode for function calls */ +static void VMCallOp(VM * vm) { + uint32_t ret = vm->pc[1]; + uint32_t arity = vm->pc[2]; + Value callee = *VMOpArg(3); + uint32_t i; + Value * argWriter; + FramePC(vm->thread) = vm->pc + 4 + arity; + VMPushCallee(vm, ret, arity, callee); + argWriter = vm->base; + if (callee.type == TYPE_CFUNCTION) { + for (i = 0; i < arity; ++i) + *(argWriter++) = *VMOpArg(4 + i); + VMReturn(vm, callee.data.cfunction(vm)); + VMMaybeCollect(vm); + } else if (callee.type == TYPE_FUNCTION) { + Func * f = callee.data.func; + uint32_t extraNils = f->def->locals; + if (arity > f->def->arity) { + arity = f->def->arity; + } else if (arity < f->def->arity) { + extraNils += f->def->arity - arity; + } + for (i = 0; i < arity; ++i) + *(argWriter++) = *VMOpArg(4 + i); + for (i = 0; i < extraNils; ++i) + (argWriter++)->type = TYPE_NIL; + vm->pc = f->def->byteCode; + } else { + VMError(vm, EXPECTED_FUNCTION); + } +} + +/* Implementation of the opcode for tail calls */ +static void VMTailCallOp(VM * vm) { + GC * gc = &vm->gc; + uint32_t arity = vm->pc[1]; + Value callee = *VMOpArg(2); + Value * extra, * argWriter; + Array * thread = vm->thread; + uint16_t newFrameSize; + uint32_t i; + /* Check for closures */ + if (FrameEnvValue(thread).type == TYPE_FUNCENV) { + FuncEnv * env = FrameEnv(thread); + uint16_t frameSize = FrameSize(thread); + Value * envValues = GCAlloc(gc, FrameSize(thread) * sizeof(Value)); + env->values = envValues; + memcpy(envValues, vm->base, frameSize * sizeof(Value)); + env->stackOffset = frameSize; + env->thread = NULL; + } + if (callee.type == TYPE_CFUNCTION) { + newFrameSize = arity; + } else if (callee.type == TYPE_FUNCTION) { + Func * f = callee.data.func; + newFrameSize = f->def->locals; + } else { + VMError(vm, EXPECTED_FUNCTION); + } + /* Ensure that stack is zeroed in this spot */ + ArrayEnsure(&vm->gc, thread, thread->count + newFrameSize + arity); + vm->base = ThreadStack(thread); + extra = argWriter = vm->base + FrameSize(thread) + FRAME_SIZE; + for (i = 0; i < arity; ++i) { + *argWriter++ = *VMOpArg(3 + i); + } + /* Copy the end of the stack to the parameter position */ + memcpy(vm->base, extra, arity * sizeof(Value)); + /* nil the new stack for gc */ + argWriter = vm->base + arity; + for (i = arity; i < newFrameSize; ++i) { + (argWriter++)->type = TYPE_NIL; + } + FrameSize(thread) = newFrameSize; + FrameCallee(thread) = callee; + if (callee.type == TYPE_CFUNCTION) { + VMReturn(vm, callee.data.cfunction(vm)); + VMMaybeCollect(vm); + } else { + Func * f = callee.data.func; + vm->pc = f->def->byteCode; + } +} + +/* Instantiate a closure */ +static void VMMakeClosure(VM * vm, uint16_t ret, uint16_t literal) { + Value * vRet = VMArg(ret); + if (FrameCallee(vm->thread).type != TYPE_FUNCTION) { + VMError(vm, EXPECTED_FUNCTION); + } else { + Func * fn, * current; + Value * constant; + Array * thread = vm->thread; + FuncEnv * env = FrameEnv(vm->thread); + if (!env) { + env = GCAlloc(&vm->gc, sizeof(FuncEnv)); + env->thread = thread; + env->stackOffset = thread->count; + env->values = NULL; + FrameEnvValue(vm->thread).data.funcenv = env; + FrameEnvValue(vm->thread).type = TYPE_FUNCENV; + } + current = FrameCallee(vm->thread).data.func; + constant = LoadConstant(vm, current, literal); + if (constant->type != TYPE_FUNCDEF) { + VMError(vm, EXPECTED_FUNCTION); + } + fn = GCAlloc(&vm->gc, sizeof(Func)); + fn->def = constant->data.funcdef; + fn->parent = current; + fn->env = env; + vRet->type = TYPE_FUNCTION; + vRet->data.func = fn; + VMMaybeCollect(vm); + } +} + +/* Start running the VM */ +int VMStart(VM * vm) { + + /* Set jmp_buf to jump back to for return. */ + { + int n; + if ((n = setjmp(vm->jump))) { + /* Good return */ + if (n == 1) { + return 0; + } else { + /* Error */ + return n; + } + } + } + + for (;;) { + uint16_t opcode = *vm->pc; + + switch (opcode) { + Value *vRet, *v1, *v2; + + case VM_OP_ADD: /* Addition */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + VMAssert(vm, v1->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_LOP); + VMAssert(vm, v2->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_ROP); + vRet->type = TYPE_NUMBER; + vRet->data.number = v1->data.number + v2->data.number; + vm->pc += 4; + break; + + case VM_OP_SUB: /* Subtraction */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + VMAssert(vm, v1->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_LOP); + VMAssert(vm, v2->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_ROP); + vRet->type = TYPE_NUMBER; + vRet->data.number = v1->data.number - v2->data.number; + vm->pc += 4; + break; + + case VM_OP_MUL: /* Multiplication */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + VMAssert(vm, v1->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_LOP); + VMAssert(vm, v2->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_ROP); + vRet->type = TYPE_NUMBER; + vRet->data.number = v1->data.number * v2->data.number; + vm->pc += 4; + break; + + case VM_OP_DIV: /* Division */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + VMAssert(vm, v1->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_LOP); + VMAssert(vm, v2->type == TYPE_NUMBER, VMS_EXPECTED_NUMBER_ROP); + vRet->type = TYPE_NUMBER; + vRet->data.number = v1->data.number / v2->data.number; + vm->pc += 4; + break; + + case VM_OP_NOT: /* Boolean unary (Boolean not) */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + vm->pc += 3; + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = !truthy(v1); + break; + + case VM_OP_LD0: /* Load 0 */ + vRet = VMOpArg(1); + vRet->type = TYPE_NUMBER; + vRet->data.number = 0; + vm->pc += 2; + break; + + case VM_OP_LD1: /* Load 1 */ + vRet = VMOpArg(1); + vRet->type = TYPE_NUMBER; + vRet->data.number = 1; + vm->pc += 2; + break; + + case VM_OP_FLS: /* Load False */ + vRet = VMOpArg(1); + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = 0; + vm->pc += 2; + break; + + case VM_OP_TRU: /* Load True */ + vRet = VMOpArg(1); + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = 1; + vm->pc += 2; + break; + + case VM_OP_NIL: /* Load Nil */ + vRet = VMOpArg(1); + vRet->type = TYPE_NIL; + vm->pc += 2; + break; + + case VM_OP_I16: /* Load Small Integer */ + vRet = VMOpArg(1); + vRet->type = TYPE_NUMBER; + vRet->data.number = ((int16_t *)(vm->pc))[2]; + vm->pc += 3; + break; + + case VM_OP_UPV: /* Load Up Value */ + { + Value callee; + callee = FrameCallee(vm->thread); + VMAssert(vm, callee.type == TYPE_FUNCTION, EXPECTED_FUNCTION); + vRet = VMOpArg(1); + *vRet = *GetUpValue(vm, callee.data.func, vm->pc[2], vm->pc[3]); + vm->pc += 4; + } + break; + + case VM_OP_JIF: /* Jump If */ + if (truthy(VMOpArg(1))) { + vm->pc += 4; + } else { + vm->pc += *((int32_t *)(vm->pc + 2)); + } + break; + + case VM_OP_JMP: /* Jump */ + vm->pc += *((int32_t *)(vm->pc + 1)); + break; + + case VM_OP_CAL: /* Call */ + VMCallOp(vm); + break; + + case VM_OP_RET: /* Return */ + VMReturn(vm, *VMOpArg(1)); + break; + + case VM_OP_SUV: /* Set Up Value */ + VMAssert(vm, FrameCallee(vm->thread).type == TYPE_FUNCTION, EXPECTED_FUNCTION); + vRet = VMOpArg(1); + *GetUpValue(vm, FrameCallee(vm->thread).data.func, vm->pc[2], vm->pc[3]) = *vRet; + vm->pc += 4; + break; + + case VM_OP_CST: /* Load constant value */ + VMAssert(vm, FrameCallee(vm->thread).type == TYPE_FUNCTION, EXPECTED_FUNCTION); + vRet = VMOpArg(1); + *vRet = *LoadConstant(vm, FrameCallee(vm->thread).data.func, vm->pc[2]); + vm->pc += 3; + break; + + case VM_OP_I32: /* Load 32 bit integer */ + vRet = VMOpArg(1); + vRet->type = TYPE_NUMBER; + vRet->data.number = *((int32_t *)(vm->pc + 2)); + vm->pc += 4; + break; + + case VM_OP_F64: /* Load 64 bit float */ + vRet = VMOpArg(1); + vRet->type = TYPE_NUMBER; + vRet->data.number = (Number) *((double *)(vm->pc + 2)); + vm->pc += 6; + break; + + case VM_OP_MOV: /* Move Values */ + vRet = VMOpArg(1); + v1 = vm->base + *((uint32_t *)(vm->pc + 2)); + *vRet = *v1; + vm->pc += 4; + break; + + case VM_OP_CLN: /* Create closure from constant FuncDef */ + VMMakeClosure(vm, vm->pc[1], vm->pc[2]); + vm->pc += 3; + break; + + case VM_OP_EQL: /* Equality */ + vRet = VMOpArg(1); + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = ValueEqual(VMOpArg(2), VMOpArg(3)); + vm->pc += 4; + break; + + case VM_OP_LTN: /* Less Than */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = (ValueCompare(VMOpArg(2), VMOpArg(3)) == -1); + vm->pc += 4; + break; + + case VM_OP_LTE: /* Less Than or Equal to */ + vRet = VMOpArg(1); + v1 = VMOpArg(2); + v2 = VMOpArg(3); + vRet->type = TYPE_BOOLEAN; + vRet->data.boolean = (ValueCompare(VMOpArg(2), VMOpArg(3)) != 1); + vm->pc += 4; + break; + + case VM_OP_ARR: /* Array literal */ + vRet = VMOpArg(1); + { + uint32_t i; + uint32_t arrayLen = vm->pc[2]; + Array * array = ArrayNew(&vm->gc, arrayLen); + array->count = arrayLen; + for (i = 0; i < arrayLen; ++i) + array->data[i] = *VMOpArg(3 + i); + vRet->type = TYPE_ARRAY; + vRet->data.array = array; + vm->pc += 3 + arrayLen; + VMMaybeCollect(vm); + } + break; + + case VM_OP_DIC: /* Dictionary literal */ + vRet = VMOpArg(1); + { + uint32_t i = 3; + uint32_t kvs = vm->pc[2]; + Dictionary * dict = DictNew(&vm->gc, kvs); + kvs = kvs * 2 + 3; + while (i < kvs) { + v1 = VMOpArg(i++); + v2 = VMOpArg(i++); + DictPut(&vm->gc, dict, v1, v2); + } + vRet->type = TYPE_DICTIONARY; + vRet->data.dict = dict; + vm->pc += kvs; + VMMaybeCollect(vm); + } + break; + + case VM_OP_TCL: /* Tail call */ + VMTailCallOp(vm); + break; + + /* Macro for generating some math operators */ + #define DO_MULTI_MATH(op, start) { \ + uint16_t i; \ + uint16_t count = vm->pc[1]; \ + Number accum = start; \ + vRet = VMOpArg(2); \ + for (i = 0; i < count; ++i) { \ + Value * x = VMOpArg(3 + i); \ + VMAssert(vm, x->type == TYPE_NUMBER, "Expected number"); \ + accum = accum op x->data.number; \ + } \ + vRet->type = TYPE_NUMBER; vRet->data.number = accum; \ + vm->pc += 3 + count; \ + } + + /* Vectorized math */ + case VM_OP_ADM: + DO_MULTI_MATH(+, 0) + break; + + case VM_OP_SBM: + DO_MULTI_MATH(-, 0) + break; + + case VM_OP_MUM: + DO_MULTI_MATH(*, 1) + break; + + case VM_OP_DVM: + DO_MULTI_MATH(/, 1) + break; + + #undef DO_MULTI_MATH + + case VM_OP_RTN: /* Return nil */ + { + Value temp; + temp.type = TYPE_NIL; + VMReturn(vm, temp); + } + break; + + default: + VMError(vm, "Unknown opcode"); + break; + } + } +} + +/* Initialize the VM */ +void VMInit(VM * vm) { + GCInit(&vm->gc, 0); + vm->gc.handleOutOfMemory = VMHandleOutOfMemory; + vm->tempRoot.type = TYPE_NIL; + vm->base = NULL; + vm->pc = NULL; + vm->error = NULL; + vm->thread = ArrayNew(&vm->gc, 20); +} + +/* Load a function into the VM. The function will be called with + * no arguments when run */ +void VMLoad(VM * vm, Func * func) { + Value callee; + callee.type = TYPE_FUNCTION; + callee.data.func = func; + vm->thread = ArrayNew(&vm->gc, 20); + VMThreadPush(vm, vm->thread, callee, func->def->locals); + vm->pc = func->def->byteCode; +} + +/* Clear all memory associated with the VM */ +void VMDeinit(VM * vm) { + GCClear(&vm->gc); +} + +#undef VMOpArg +#undef VMArg diff --git a/vm.h b/vm.h new file mode 100644 index 00000000..0774aabe --- /dev/null +++ b/vm.h @@ -0,0 +1,41 @@ +#ifndef VM_H_C4OZU8CQ +#define VM_H_C4OZU8CQ + +#include "datatypes.h" + +/* Exit from the VM normally */ +#define VMExit(vm, r) ((vm)->tempRoot = (r), longjmp((vm)->jump, 1)) + +/* Bail from the VM with an error. */ +#define VMError(vm, e) ((vm)->error = (e), longjmp((vm)->jump, 2)) + +/* Error if the condition is false */ +#define VMAssert(vm, cond, e) do \ + { if (!(cond)) { VMError((vm), (e)); } } while (0) + +/* Initialize the VM */ +void VMInit(VM * vm); + +/* Deinitialize the VM */ +void VMDeinit(VM * vm); + +/* Load a function to be run on the VM */ +void VMLoad(VM * vm, Func * func); + +/* Start running the VM */ +int VMStart(VM * vm); + +/* Get the result after VMStart returns */ +#define VMResult(vm) ((vm)->tempRoot) + +/* Mark memory reachable by VM */ +void VMMark(VM * vm); + +/* Run garbage collection */ +void VMCollect(VM * vm); + +/* Collect garbage if enough memory has been allocated since + * the previous collection */ +void VMMaybeCollect(VM * vm); + +#endif /* end of include guard: VM_H_C4OZU8CQ */ diff --git a/vstring.h b/vstring.h new file mode 100644 index 00000000..415bfcdc --- /dev/null +++ b/vstring.h @@ -0,0 +1,10 @@ +#ifndef VSTRING_H_TFCSVBEE +#define VSTRING_H_TFCSVBEE + +#include "datatypes.h" + +#define VStringRaw(s) ((uint32_t *)(s) - 2) +#define VStringSize(v) (VStringRaw(v)[0]) +#define VStringHash(v) (VStringRaw(v)[1]) + +#endif /* end of include guard: VSTRING_H_TFCSVBEE */