Wren testing
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
#ifndef wren_common_h
|
||||
#define wren_common_h
|
||||
|
||||
// This header contains macros and defines used across the entire Wren
|
||||
// implementation. In particular, it contains "configuration" defines that
|
||||
// control how Wren works. Some of these are only used while hacking on Wren
|
||||
// itself.
|
||||
//
|
||||
// This header is *not* intended to be included by code outside of Wren itself.
|
||||
|
||||
// Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some
|
||||
// of the associated limit constants (UINT32_MAX, etc.). The constants are not
|
||||
// part of standard C++, so aren't included by default by C++ compilers when you
|
||||
// include <stdint> unless __STDC_LIMIT_MACROS is defined.
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
// These flags let you control some details of the interpreter's implementation.
|
||||
// Usually they trade-off a bit of portability for speed. They default to the
|
||||
// most efficient behavior.
|
||||
|
||||
// If true, then Wren uses a NaN-tagged double for its core value
|
||||
// representation. Otherwise, it uses a larger more conventional struct. The
|
||||
// former is significantly faster and more compact. The latter is useful for
|
||||
// debugging and may be more portable.
|
||||
//
|
||||
// Defaults to on.
|
||||
#ifndef WREN_NAN_TAGGING
|
||||
#define WREN_NAN_TAGGING 1
|
||||
#endif
|
||||
|
||||
// If true, the VM's interpreter loop uses computed gotos. See this for more:
|
||||
// http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
|
||||
// Enabling this speeds up the main dispatch loop a bit, but requires compiler
|
||||
// support.
|
||||
// see https://bullno1.com/blog/switched-goto for alternative
|
||||
// Defaults to true on supported compilers.
|
||||
#ifndef WREN_COMPUTED_GOTO
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
// No computed gotos in Visual Studio.
|
||||
#define WREN_COMPUTED_GOTO 0
|
||||
#else
|
||||
#define WREN_COMPUTED_GOTO 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// The VM includes a number of optional modules. You can choose to include
|
||||
// these or not. By default, they are all available. To disable one, set the
|
||||
// corresponding `WREN_OPT_<name>` define to `0`.
|
||||
#ifndef WREN_OPT_META
|
||||
#define WREN_OPT_META 1
|
||||
#endif
|
||||
|
||||
#ifndef WREN_OPT_RANDOM
|
||||
#define WREN_OPT_RANDOM 1
|
||||
#endif
|
||||
|
||||
// These flags are useful for debugging and hacking on Wren itself. They are not
|
||||
// intended to be used for production code. They default to off.
|
||||
|
||||
// Set this to true to stress test the GC. It will perform a collection before
|
||||
// every allocation. This is useful to ensure that memory is always correctly
|
||||
// reachable.
|
||||
#define WREN_DEBUG_GC_STRESS 0
|
||||
|
||||
// Set this to true to log memory operations as they occur.
|
||||
#define WREN_DEBUG_TRACE_MEMORY 0
|
||||
|
||||
// Set this to true to log garbage collections as they occur.
|
||||
#define WREN_DEBUG_TRACE_GC 0
|
||||
|
||||
// Set this to true to print out the compiled bytecode of each function.
|
||||
#define WREN_DEBUG_DUMP_COMPILED_CODE 0
|
||||
|
||||
// Set this to trace each instruction as it's executed.
|
||||
#define WREN_DEBUG_TRACE_INSTRUCTIONS 0
|
||||
|
||||
// The maximum number of module-level variables that may be defined at one time.
|
||||
// This limitation comes from the 16 bits used for the arguments to
|
||||
// `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`.
|
||||
#define MAX_MODULE_VARS 65536
|
||||
|
||||
// The maximum number of arguments that can be passed to a method. Note that
|
||||
// this limitation is hardcoded in other places in the VM, in particular, the
|
||||
// `CODE_CALL_XX` instructions assume a certain maximum number.
|
||||
#define MAX_PARAMETERS 16
|
||||
|
||||
// The maximum name of a method, not including the signature. This is an
|
||||
// arbitrary but enforced maximum just so we know how long the method name
|
||||
// strings need to be in the parser.
|
||||
#define MAX_METHOD_NAME 64
|
||||
|
||||
// The maximum length of a method signature. Signatures look like:
|
||||
//
|
||||
// foo // Getter.
|
||||
// foo() // No-argument method.
|
||||
// foo(_) // One-argument method.
|
||||
// foo(_,_) // Two-argument method.
|
||||
// init foo() // Constructor initializer.
|
||||
//
|
||||
// The maximum signature length takes into account the longest method name, the
|
||||
// maximum number of parameters with separators between them, "init ", and "()".
|
||||
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6)
|
||||
|
||||
// The maximum length of an identifier. The only real reason for this limitation
|
||||
// is so that error messages mentioning variables can be stack allocated.
|
||||
#define MAX_VARIABLE_NAME 64
|
||||
|
||||
// The maximum number of fields a class can have, including inherited fields.
|
||||
// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
|
||||
// a single byte for the number of fields. Note that it's 255 and not 256
|
||||
// because creating a class takes the *number* of fields, not the *highest
|
||||
// field index*.
|
||||
#define MAX_FIELDS 255
|
||||
|
||||
// The maximum number of methods the vm can handle.
|
||||
#define MAX_METHODS 65535
|
||||
|
||||
// Use the VM's allocator to allocate an object of [type].
|
||||
#define ALLOCATE(vm, type) \
|
||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
|
||||
|
||||
// Use the VM's allocator to allocate an object of [mainType] containing a
|
||||
// flexible array of [count] objects of [arrayType].
|
||||
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
|
||||
((mainType*)wrenReallocate(vm, NULL, 0, \
|
||||
sizeof(mainType) + sizeof(arrayType) * (count)))
|
||||
|
||||
// Use the VM's allocator to allocate an array of [count] elements of [type].
|
||||
#define ALLOCATE_ARRAY(vm, type, count) \
|
||||
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count)))
|
||||
|
||||
// Use the VM's allocator to free the previously allocated memory at [pointer].
|
||||
#define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0)
|
||||
|
||||
// The Microsoft compiler does not support the "inline" modifier when compiling
|
||||
// as plain C.
|
||||
#if defined( _MSC_VER ) && !defined(__cplusplus)
|
||||
#define inline _inline
|
||||
#endif
|
||||
|
||||
// This is used to clearly mark flexible-sized arrays that appear at the end of
|
||||
// some dynamically-allocated structs, known as the "struct hack".
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
// In C99, a flexible array member is just "[]".
|
||||
#define FLEXIBLE_ARRAY
|
||||
#else
|
||||
// Elsewhere, use a zero-sized array. It's technically undefined behavior,
|
||||
// but works reliably in most known compilers.
|
||||
#define FLEXIBLE_ARRAY 0
|
||||
#endif
|
||||
|
||||
// Assertions are used to validate program invariants. They indicate things the
|
||||
// program expects to be true about its internal state during execution. If an
|
||||
// assertion fails, there is a bug in Wren.
|
||||
//
|
||||
// Assertions add significant overhead, so are only enabled in debug builds.
|
||||
#ifdef DEBUG
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define ASSERT(condition, message) \
|
||||
do \
|
||||
{ \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
|
||||
__FILE__, __LINE__, __func__, message); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// Indicates that we know execution should never reach this point in the
|
||||
// program. In debug mode, we assert this fact because it's a bug to get here.
|
||||
//
|
||||
// In release mode, we use compiler-specific built in functions to tell the
|
||||
// compiler the code can't be reached. This avoids "missing return" warnings
|
||||
// in some cases and also lets it perform some optimizations by assuming the
|
||||
// code is never reached.
|
||||
#define UNREACHABLE() \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \
|
||||
__FILE__, __LINE__, __func__); \
|
||||
abort(); \
|
||||
} while (false)
|
||||
|
||||
#else
|
||||
|
||||
#define ASSERT(condition, message) do { } while (false)
|
||||
|
||||
// Tell the compiler that this part of the code will never be reached.
|
||||
#if defined( _MSC_VER )
|
||||
#define UNREACHABLE() __assume(0)
|
||||
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||
#define UNREACHABLE() __builtin_unreachable()
|
||||
#else
|
||||
#define UNREACHABLE()
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
#ifndef wren_compiler_h
|
||||
#define wren_compiler_h
|
||||
|
||||
#include "wren.h"
|
||||
#include "wren_value.h"
|
||||
|
||||
typedef struct sCompiler Compiler;
|
||||
|
||||
// This module defines the compiler for Wren. It takes a string of source code
|
||||
// and lexes, parses, and compiles it. Wren uses a single-pass compiler. It
|
||||
// does not build an actual AST during parsing and then consume that to
|
||||
// generate code. Instead, the parser directly emits bytecode.
|
||||
//
|
||||
// This forces a few restrictions on the grammar and semantics of the language.
|
||||
// Things like forward references and arbitrary lookahead are much harder. We
|
||||
// get a lot in return for that, though.
|
||||
//
|
||||
// The implementation is much simpler since we don't need to define a bunch of
|
||||
// AST data structures. More so, we don't have to deal with managing memory for
|
||||
// AST objects. The compiler does almost no dynamic allocation while running.
|
||||
//
|
||||
// Compilation is also faster since we don't create a bunch of temporary data
|
||||
// structures and destroy them after generating code.
|
||||
|
||||
// Compiles [source], a string of Wren source code located in [module], to an
|
||||
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
|
||||
// source contains any syntax errors.
|
||||
//
|
||||
// If [isExpression] is `true`, [source] should be a single expression, and
|
||||
// this compiles it to a function that evaluates and returns that expression.
|
||||
// Otherwise, [source] should be a series of top level statements.
|
||||
//
|
||||
// If [printErrors] is `true`, any compile errors are output to stderr.
|
||||
// Otherwise, they are silently discarded.
|
||||
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
||||
bool isExpression, bool printErrors);
|
||||
|
||||
// When a class is defined, its superclass is not known until runtime since
|
||||
// class definitions are just imperative statements. Most of the bytecode for a
|
||||
// a method doesn't care, but there are two places where it matters:
|
||||
//
|
||||
// - To load or store a field, we need to know the index of the field in the
|
||||
// instance's field array. We need to adjust this so that subclass fields
|
||||
// are positioned after superclass fields, and we don't know this until the
|
||||
// superclass is known.
|
||||
//
|
||||
// - Superclass calls need to know which superclass to dispatch to.
|
||||
//
|
||||
// We could handle this dynamically, but that adds overhead. Instead, when a
|
||||
// method is bound, we walk the bytecode for the function and patch it up.
|
||||
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn);
|
||||
|
||||
// Reaches all of the heap-allocated objects in use by [compiler] (and all of
|
||||
// its parents) so that they are not collected by the GC.
|
||||
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler);
|
||||
|
||||
#endif
|
||||
+1487
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
#ifndef wren_core_h
|
||||
#define wren_core_h
|
||||
|
||||
#include "wren_vm.h"
|
||||
|
||||
// This module defines the built-in classes and their primitives methods that
|
||||
// are implemented directly in C code. Some languages try to implement as much
|
||||
// of the core module itself in the primary language instead of in the host
|
||||
// language.
|
||||
//
|
||||
// With Wren, we try to do as much of it in C as possible. Primitive methods
|
||||
// are always faster than code written in Wren, and it minimizes startup time
|
||||
// since we don't have to parse, compile, and execute Wren code.
|
||||
//
|
||||
// There is one limitation, though. Methods written in C cannot call Wren ones.
|
||||
// They can only be the top of the callstack, and immediately return. This
|
||||
// makes it difficult to have primitive methods that rely on polymorphic
|
||||
// behavior. For example, `System.print` should call `toString` on its argument,
|
||||
// including user-defined `toString` methods on user-defined classes.
|
||||
|
||||
void wrenInitializeCore(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,483 @@
|
||||
class Bool {}
|
||||
class Fiber {}
|
||||
class Fn {}
|
||||
class Null {}
|
||||
class Num {}
|
||||
|
||||
class Sequence {
|
||||
all(f) {
|
||||
var result = true
|
||||
for (element in this) {
|
||||
result = f.call(element)
|
||||
if (!result) return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
any(f) {
|
||||
var result = false
|
||||
for (element in this) {
|
||||
result = f.call(element)
|
||||
if (result) return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
contains(element) {
|
||||
for (item in this) {
|
||||
if (element == item) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
count {
|
||||
var result = 0
|
||||
for (element in this) {
|
||||
result = result + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
count(f) {
|
||||
var result = 0
|
||||
for (element in this) {
|
||||
if (f.call(element)) result = result + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
each(f) {
|
||||
for (element in this) {
|
||||
f.call(element)
|
||||
}
|
||||
}
|
||||
|
||||
isEmpty { iterate(null) ? false : true }
|
||||
|
||||
map(transformation) { MapSequence.new(this, transformation) }
|
||||
|
||||
skip(count) {
|
||||
if (!(count is Num) || !count.isInteger || count < 0) {
|
||||
Fiber.abort("Count must be a non-negative integer.")
|
||||
}
|
||||
|
||||
return SkipSequence.new(this, count)
|
||||
}
|
||||
|
||||
take(count) {
|
||||
if (!(count is Num) || !count.isInteger || count < 0) {
|
||||
Fiber.abort("Count must be a non-negative integer.")
|
||||
}
|
||||
|
||||
return TakeSequence.new(this, cournt)
|
||||
}
|
||||
|
||||
where(predicate) { WhereSequence.new(this, predicate) }
|
||||
|
||||
reduce(acc, f) {
|
||||
for (element in this) {
|
||||
acc = f.call(acc, element)
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
reduce(f) {
|
||||
var iter = iterate(null)
|
||||
if (!iter) Fiber.abort("Can't reduce an empty sequence.")
|
||||
|
||||
// Seed with the first element.
|
||||
var result = iteratorValue(iter)
|
||||
while (iter = iterate(iter)) {
|
||||
result = f.call(result, iteratorValue(iter))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
join() { join("") }
|
||||
|
||||
join(sep) {
|
||||
var first = true
|
||||
var result = ""
|
||||
|
||||
for (element in this) {
|
||||
if (!first) result = result + sep
|
||||
first = false
|
||||
result = result + element.toString
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
toList {
|
||||
var result = List.new()
|
||||
for (element in this) {
|
||||
result.add(element)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class MapSequence is Sequence {
|
||||
construct new(sequence, fn) {
|
||||
_sequence = sequence
|
||||
_fn = fn
|
||||
}
|
||||
|
||||
iterate(iterator) { _sequence.iterate(iterator) }
|
||||
iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }
|
||||
}
|
||||
|
||||
class SkipSequence is Sequence {
|
||||
construct new(sequence, count) {
|
||||
_sequence = sequence
|
||||
_count = count
|
||||
}
|
||||
|
||||
iterate(iterator) {
|
||||
if (iterator) {
|
||||
return _sequence.iterate(iterator)
|
||||
} else {
|
||||
iterator = _sequence.iterate(iterator)
|
||||
var count = _count
|
||||
while (count > 0 && iterator) {
|
||||
iterator = _sequence.iterate(iterator)
|
||||
count = count - 1
|
||||
}
|
||||
return iterator
|
||||
}
|
||||
}
|
||||
|
||||
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
|
||||
}
|
||||
|
||||
class TakeSequence is Sequence {
|
||||
construct new(sequence, count) {
|
||||
_sequence = sequence
|
||||
_count = count
|
||||
}
|
||||
|
||||
iterate(iterator) {
|
||||
if (!iterator) _taken = 1 else _taken = _taken + 1
|
||||
return _taken > _count ? null : _sequence.iterate(iterator)
|
||||
}
|
||||
|
||||
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
|
||||
}
|
||||
|
||||
class WhereSequence is Sequence {
|
||||
construct new(sequence, fn) {
|
||||
_sequence = sequence
|
||||
_fn = fn
|
||||
}
|
||||
|
||||
iterate(iterator) {
|
||||
while (iterator = _sequence.iterate(iterator)) {
|
||||
if (_fn.call(_sequence.iteratorValue(iterator))) break
|
||||
}
|
||||
return iterator
|
||||
}
|
||||
|
||||
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
|
||||
}
|
||||
|
||||
class String is Sequence {
|
||||
bytes { StringByteSequence.new(this) }
|
||||
codePoints { StringCodePointSequence.new(this) }
|
||||
|
||||
split(delimiter) {
|
||||
if (!(delimiter is String) || delimiter.isEmpty) {
|
||||
Fiber.abort("Delimiter must be a non-empty string.")
|
||||
}
|
||||
|
||||
var result = []
|
||||
|
||||
var last = 0
|
||||
var index = 0
|
||||
|
||||
var delimSize = delimiter.byteCount_
|
||||
var size = byteCount_
|
||||
|
||||
while (last < size && (index = indexOf(delimiter, last)) != -1) {
|
||||
result.add(this[last...index])
|
||||
last = index + delimSize
|
||||
}
|
||||
|
||||
if (last < size) {
|
||||
result.add(this[last..-1])
|
||||
} else {
|
||||
result.add("")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
replace(from, to) {
|
||||
if (!(from is String) || from.isEmpty) {
|
||||
Fiber.abort("From must be a non-empty string.")
|
||||
} else if (!(to is String)) {
|
||||
Fiber.abort("To must be a string.")
|
||||
}
|
||||
|
||||
var result = ""
|
||||
|
||||
var last = 0
|
||||
var index = 0
|
||||
|
||||
var fromSize = from.byteCount_
|
||||
var size = byteCount_
|
||||
|
||||
while (last < size && (index = indexOf(from, last)) != -1) {
|
||||
result = result + this[last...index] + to
|
||||
last = index + fromSize
|
||||
}
|
||||
|
||||
if (last < size) result = result + this[last..-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
trim() { trim_("\t\r\n ", true, true) }
|
||||
trim(chars) { trim_(chars, true, true) }
|
||||
trimEnd() { trim_("\t\r\n ", false, true) }
|
||||
trimEnd(chars) { trim_(chars, false, true) }
|
||||
trimStart() { trim_("\t\r\n ", true, false) }
|
||||
trimStart(chars) { trim_(chars, true, false) }
|
||||
|
||||
trim_(chars, trimStart, trimEnd) {
|
||||
if (!(chars is String)) {
|
||||
Fiber.abort("Characters must be a string.")
|
||||
}
|
||||
|
||||
var codePoints = chars.codePoints.toList
|
||||
|
||||
var start
|
||||
if (trimStart) {
|
||||
while (start = iterate(start)) {
|
||||
if (!codePoints.contains(codePointAt_(start))) break
|
||||
}
|
||||
|
||||
if (start == false) return ""
|
||||
} else {
|
||||
start = 0
|
||||
}
|
||||
|
||||
var end
|
||||
if (trimEnd) {
|
||||
end = byteCount_ - 1
|
||||
while (end >= start) {
|
||||
var codePoint = codePointAt_(end)
|
||||
if (codePoint != -1 && !codePoints.contains(codePoint)) break
|
||||
end = end - 1
|
||||
}
|
||||
|
||||
if (end < start) return ""
|
||||
} else {
|
||||
end = -1
|
||||
}
|
||||
|
||||
return this[start..end]
|
||||
}
|
||||
|
||||
*(count) {
|
||||
if (!(count is Num) || !count.isInteger || count < 0) {
|
||||
Fiber.abort("Count must be a non-negative integer.")
|
||||
}
|
||||
|
||||
var result = ""
|
||||
for (i in 0...count) {
|
||||
result = result + this
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class StringByteSequence is Sequence {
|
||||
construct new(string) {
|
||||
_string = string
|
||||
}
|
||||
|
||||
[index] { _string.byteAt_(index) }
|
||||
iterate(iterator) { _string.iterateByte_(iterator) }
|
||||
iteratorValue(iterator) { _string.byteAt_(iterator) }
|
||||
|
||||
count { _string.byteCount_ }
|
||||
}
|
||||
|
||||
class StringCodePointSequence is Sequence {
|
||||
construct new(string) {
|
||||
_string = string
|
||||
}
|
||||
|
||||
[index] { _string.codePointAt_(index) }
|
||||
iterate(iterator) { _string.iterate(iterator) }
|
||||
iteratorValue(iterator) { _string.codePointAt_(iterator) }
|
||||
|
||||
count { _string.count }
|
||||
}
|
||||
|
||||
class List is Sequence {
|
||||
addAll(other) {
|
||||
for (element in other) {
|
||||
add(element)
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
sort() { sort {|low, high| low < high } }
|
||||
|
||||
sort(comparer) {
|
||||
if (!(comparer is Fn)) {
|
||||
Fiber.abort("Comparer must be a function.")
|
||||
}
|
||||
quicksort_(0, count - 1, comparer)
|
||||
return this
|
||||
}
|
||||
|
||||
quicksort_(low, high, comparer) {
|
||||
if (low < high) {
|
||||
var p = partition_(low, high, comparer)
|
||||
quicksort_(low, p - 1, comparer)
|
||||
quicksort_(p + 1, high, comparer)
|
||||
}
|
||||
}
|
||||
|
||||
partition_(low, high, comparer) {
|
||||
var p = this[high]
|
||||
var i = low - 1
|
||||
for (j in low..(high-1)) {
|
||||
if (comparer.call(this[j], p)) {
|
||||
i = i + 1
|
||||
var t = this[i]
|
||||
this[i] = this[j]
|
||||
this[j] = t
|
||||
}
|
||||
}
|
||||
var t = this[i+1]
|
||||
this[i+1] = this[high]
|
||||
this[high] = t
|
||||
return i+1
|
||||
}
|
||||
|
||||
toString { "[%(join(", "))]" }
|
||||
|
||||
+(other) {
|
||||
var result = this[0..-1]
|
||||
for (element in other) {
|
||||
result.add(element)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
*(count) {
|
||||
if (!(count is Num) || !count.isInteger || count < 0) {
|
||||
Fiber.abort("Count must be a non-negative integer.")
|
||||
}
|
||||
|
||||
var result = []
|
||||
for (i in 0...count) {
|
||||
result.addAll(this)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Map is Sequence {
|
||||
keys { MapKeySequence.new(this) }
|
||||
values { MapValueSequence.new(this) }
|
||||
|
||||
toString {
|
||||
var first = true
|
||||
var result = "{"
|
||||
|
||||
for (key in keys) {
|
||||
if (!first) result = result + ", "
|
||||
first = false
|
||||
result = result + "%(key): %(this[key])"
|
||||
}
|
||||
|
||||
return result + "}"
|
||||
}
|
||||
|
||||
iteratorValue(iterator) {
|
||||
return MapEntry.new(
|
||||
keyIteratorValue_(iterator),
|
||||
valueIteratorValue_(iterator))
|
||||
}
|
||||
}
|
||||
|
||||
class MapEntry {
|
||||
construct new(key, value) {
|
||||
_key = key
|
||||
_value = value
|
||||
}
|
||||
|
||||
key { _key }
|
||||
value { _value }
|
||||
|
||||
toString { "%(_key):%(_value)" }
|
||||
}
|
||||
|
||||
class MapKeySequence is Sequence {
|
||||
construct new(map) {
|
||||
_map = map
|
||||
}
|
||||
|
||||
iterate(n) { _map.iterate(n) }
|
||||
iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
|
||||
}
|
||||
|
||||
class MapValueSequence is Sequence {
|
||||
construct new(map) {
|
||||
_map = map
|
||||
}
|
||||
|
||||
iterate(n) { _map.iterate(n) }
|
||||
iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
|
||||
}
|
||||
|
||||
class Range is Sequence {}
|
||||
|
||||
class System {
|
||||
static print() {
|
||||
writeString_("\n")
|
||||
}
|
||||
|
||||
static print(obj) {
|
||||
writeObject_(obj)
|
||||
writeString_("\n")
|
||||
return obj
|
||||
}
|
||||
|
||||
static printAll(sequence) {
|
||||
for (object in sequence) writeObject_(object)
|
||||
writeString_("\n")
|
||||
}
|
||||
|
||||
static write(obj) {
|
||||
writeObject_(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
static writeAll(sequence) {
|
||||
for (object in sequence) writeObject_(object)
|
||||
}
|
||||
|
||||
static writeObject_(obj) {
|
||||
var string = obj.toString
|
||||
if (string is String) {
|
||||
writeString_(string)
|
||||
} else {
|
||||
writeString_("[invalid toString]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClassAttributes {
|
||||
self { _attributes }
|
||||
methods { _methods }
|
||||
construct new(attributes, methods) {
|
||||
_attributes = attributes
|
||||
_methods = methods
|
||||
}
|
||||
toString { "attributes:%(_attributes) methods:%(_methods)" }
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
// Generated automatically from src/vm/wren_core.wren. Do not edit.
|
||||
static const char* coreModuleSource =
|
||||
"class Bool {}\n"
|
||||
"class Fiber {}\n"
|
||||
"class Fn {}\n"
|
||||
"class Null {}\n"
|
||||
"class Num {}\n"
|
||||
"\n"
|
||||
"class Sequence {\n"
|
||||
" all(f) {\n"
|
||||
" var result = true\n"
|
||||
" for (element in this) {\n"
|
||||
" result = f.call(element)\n"
|
||||
" if (!result) return result\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" any(f) {\n"
|
||||
" var result = false\n"
|
||||
" for (element in this) {\n"
|
||||
" result = f.call(element)\n"
|
||||
" if (result) return result\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" contains(element) {\n"
|
||||
" for (item in this) {\n"
|
||||
" if (element == item) return true\n"
|
||||
" }\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" count {\n"
|
||||
" var result = 0\n"
|
||||
" for (element in this) {\n"
|
||||
" result = result + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" count(f) {\n"
|
||||
" var result = 0\n"
|
||||
" for (element in this) {\n"
|
||||
" if (f.call(element)) result = result + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" each(f) {\n"
|
||||
" for (element in this) {\n"
|
||||
" f.call(element)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isEmpty { iterate(null) ? false : true }\n"
|
||||
"\n"
|
||||
" map(transformation) { MapSequence.new(this, transformation) }\n"
|
||||
"\n"
|
||||
" skip(count) {\n"
|
||||
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
|
||||
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return SkipSequence.new(this, count)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" take(count) {\n"
|
||||
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
|
||||
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return TakeSequence.new(this, count)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" where(predicate) { WhereSequence.new(this, predicate) }\n"
|
||||
"\n"
|
||||
" reduce(acc, f) {\n"
|
||||
" for (element in this) {\n"
|
||||
" acc = f.call(acc, element)\n"
|
||||
" }\n"
|
||||
" return acc\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" reduce(f) {\n"
|
||||
" var iter = iterate(null)\n"
|
||||
" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n"
|
||||
"\n"
|
||||
" // Seed with the first element.\n"
|
||||
" var result = iteratorValue(iter)\n"
|
||||
" while (iter = iterate(iter)) {\n"
|
||||
" result = f.call(result, iteratorValue(iter))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" join() { join(\"\") }\n"
|
||||
"\n"
|
||||
" join(sep) {\n"
|
||||
" var first = true\n"
|
||||
" var result = \"\"\n"
|
||||
"\n"
|
||||
" for (element in this) {\n"
|
||||
" if (!first) result = result + sep\n"
|
||||
" first = false\n"
|
||||
" result = result + element.toString\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toList {\n"
|
||||
" var result = List.new()\n"
|
||||
" for (element in this) {\n"
|
||||
" result.add(element)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class MapSequence is Sequence {\n"
|
||||
" construct new(sequence, fn) {\n"
|
||||
" _sequence = sequence\n"
|
||||
" _fn = fn\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(iterator) { _sequence.iterate(iterator) }\n"
|
||||
" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class SkipSequence is Sequence {\n"
|
||||
" construct new(sequence, count) {\n"
|
||||
" _sequence = sequence\n"
|
||||
" _count = count\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(iterator) {\n"
|
||||
" if (iterator) {\n"
|
||||
" return _sequence.iterate(iterator)\n"
|
||||
" } else {\n"
|
||||
" iterator = _sequence.iterate(iterator)\n"
|
||||
" var count = _count\n"
|
||||
" while (count > 0 && iterator) {\n"
|
||||
" iterator = _sequence.iterate(iterator)\n"
|
||||
" count = count - 1\n"
|
||||
" }\n"
|
||||
" return iterator\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class TakeSequence is Sequence {\n"
|
||||
" construct new(sequence, count) {\n"
|
||||
" _sequence = sequence\n"
|
||||
" _count = count\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(iterator) {\n"
|
||||
" if (!iterator) _taken = 1 else _taken = _taken + 1\n"
|
||||
" return _taken > _count ? null : _sequence.iterate(iterator)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class WhereSequence is Sequence {\n"
|
||||
" construct new(sequence, fn) {\n"
|
||||
" _sequence = sequence\n"
|
||||
" _fn = fn\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(iterator) {\n"
|
||||
" while (iterator = _sequence.iterate(iterator)) {\n"
|
||||
" if (_fn.call(_sequence.iteratorValue(iterator))) break\n"
|
||||
" }\n"
|
||||
" return iterator\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class String is Sequence {\n"
|
||||
" bytes { StringByteSequence.new(this) }\n"
|
||||
" codePoints { StringCodePointSequence.new(this) }\n"
|
||||
"\n"
|
||||
" split(delimiter) {\n"
|
||||
" if (!(delimiter is String) || delimiter.isEmpty) {\n"
|
||||
" Fiber.abort(\"Delimiter must be a non-empty string.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var result = []\n"
|
||||
"\n"
|
||||
" var last = 0\n"
|
||||
" var index = 0\n"
|
||||
"\n"
|
||||
" var delimSize = delimiter.byteCount_\n"
|
||||
" var size = byteCount_\n"
|
||||
"\n"
|
||||
" while (last < size && (index = indexOf(delimiter, last)) != -1) {\n"
|
||||
" result.add(this[last...index])\n"
|
||||
" last = index + delimSize\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (last < size) {\n"
|
||||
" result.add(this[last..-1])\n"
|
||||
" } else {\n"
|
||||
" result.add(\"\")\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" replace(from, to) {\n"
|
||||
" if (!(from is String) || from.isEmpty) {\n"
|
||||
" Fiber.abort(\"From must be a non-empty string.\")\n"
|
||||
" } else if (!(to is String)) {\n"
|
||||
" Fiber.abort(\"To must be a string.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var result = \"\"\n"
|
||||
"\n"
|
||||
" var last = 0\n"
|
||||
" var index = 0\n"
|
||||
"\n"
|
||||
" var fromSize = from.byteCount_\n"
|
||||
" var size = byteCount_\n"
|
||||
"\n"
|
||||
" while (last < size && (index = indexOf(from, last)) != -1) {\n"
|
||||
" result = result + this[last...index] + to\n"
|
||||
" last = index + fromSize\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (last < size) result = result + this[last..-1]\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" trim() { trim_(\"\\t\\r\\n \", true, true) }\n"
|
||||
" trim(chars) { trim_(chars, true, true) }\n"
|
||||
" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n"
|
||||
" trimEnd(chars) { trim_(chars, false, true) }\n"
|
||||
" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n"
|
||||
" trimStart(chars) { trim_(chars, true, false) }\n"
|
||||
"\n"
|
||||
" trim_(chars, trimStart, trimEnd) {\n"
|
||||
" if (!(chars is String)) {\n"
|
||||
" Fiber.abort(\"Characters must be a string.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var codePoints = chars.codePoints.toList\n"
|
||||
"\n"
|
||||
" var start\n"
|
||||
" if (trimStart) {\n"
|
||||
" while (start = iterate(start)) {\n"
|
||||
" if (!codePoints.contains(codePointAt_(start))) break\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (start == false) return \"\"\n"
|
||||
" } else {\n"
|
||||
" start = 0\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var end\n"
|
||||
" if (trimEnd) {\n"
|
||||
" end = byteCount_ - 1\n"
|
||||
" while (end >= start) {\n"
|
||||
" var codePoint = codePointAt_(end)\n"
|
||||
" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n"
|
||||
" end = end - 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (end < start) return \"\"\n"
|
||||
" } else {\n"
|
||||
" end = -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return this[start..end]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" *(count) {\n"
|
||||
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
|
||||
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var result = \"\"\n"
|
||||
" for (i in 0...count) {\n"
|
||||
" result = result + this\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class StringByteSequence is Sequence {\n"
|
||||
" construct new(string) {\n"
|
||||
" _string = string\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" [index] { _string.byteAt_(index) }\n"
|
||||
" iterate(iterator) { _string.iterateByte_(iterator) }\n"
|
||||
" iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
|
||||
"\n"
|
||||
" count { _string.byteCount_ }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class StringCodePointSequence is Sequence {\n"
|
||||
" construct new(string) {\n"
|
||||
" _string = string\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" [index] { _string.codePointAt_(index) }\n"
|
||||
" iterate(iterator) { _string.iterate(iterator) }\n"
|
||||
" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
|
||||
"\n"
|
||||
" count { _string.count }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class List is Sequence {\n"
|
||||
" addAll(other) {\n"
|
||||
" for (element in other) {\n"
|
||||
" add(element)\n"
|
||||
" }\n"
|
||||
" return other\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" sort() { sort {|low, high| low < high } }\n"
|
||||
"\n"
|
||||
" sort(comparer) {\n"
|
||||
" if (!(comparer is Fn)) {\n"
|
||||
" Fiber.abort(\"Comparer must be a function.\")\n"
|
||||
" }\n"
|
||||
" quicksort_(0, count - 1, comparer)\n"
|
||||
" return this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" quicksort_(low, high, comparer) {\n"
|
||||
" if (low < high) {\n"
|
||||
" var p = partition_(low, high, comparer)\n"
|
||||
" quicksort_(low, p - 1, comparer)\n"
|
||||
" quicksort_(p + 1, high, comparer)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" partition_(low, high, comparer) {\n"
|
||||
" var p = this[high]\n"
|
||||
" var i = low - 1\n"
|
||||
" for (j in low..(high-1)) {\n"
|
||||
" if (comparer.call(this[j], p)) { \n"
|
||||
" i = i + 1\n"
|
||||
" var t = this[i]\n"
|
||||
" this[i] = this[j]\n"
|
||||
" this[j] = t\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" var t = this[i+1]\n"
|
||||
" this[i+1] = this[high]\n"
|
||||
" this[high] = t\n"
|
||||
" return i+1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toString { \"[%(join(\", \"))]\" }\n"
|
||||
"\n"
|
||||
" +(other) {\n"
|
||||
" var result = this[0..-1]\n"
|
||||
" for (element in other) {\n"
|
||||
" result.add(element)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" *(count) {\n"
|
||||
" if (!(count is Num) || !count.isInteger || count < 0) {\n"
|
||||
" Fiber.abort(\"Count must be a non-negative integer.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var result = []\n"
|
||||
" for (i in 0...count) {\n"
|
||||
" result.addAll(this)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Map is Sequence {\n"
|
||||
" keys { MapKeySequence.new(this) }\n"
|
||||
" values { MapValueSequence.new(this) }\n"
|
||||
"\n"
|
||||
" toString {\n"
|
||||
" var first = true\n"
|
||||
" var result = \"{\"\n"
|
||||
"\n"
|
||||
" for (key in keys) {\n"
|
||||
" if (!first) result = result + \", \"\n"
|
||||
" first = false\n"
|
||||
" result = result + \"%(key): %(this[key])\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result + \"}\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iteratorValue(iterator) {\n"
|
||||
" return MapEntry.new(\n"
|
||||
" keyIteratorValue_(iterator),\n"
|
||||
" valueIteratorValue_(iterator))\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class MapEntry {\n"
|
||||
" construct new(key, value) {\n"
|
||||
" _key = key\n"
|
||||
" _value = value\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" key { _key }\n"
|
||||
" value { _value }\n"
|
||||
"\n"
|
||||
" toString { \"%(_key):%(_value)\" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class MapKeySequence is Sequence {\n"
|
||||
" construct new(map) {\n"
|
||||
" _map = map\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(n) { _map.iterate(n) }\n"
|
||||
" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class MapValueSequence is Sequence {\n"
|
||||
" construct new(map) {\n"
|
||||
" _map = map\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterate(n) { _map.iterate(n) }\n"
|
||||
" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Range is Sequence {}\n"
|
||||
"\n"
|
||||
"class System {\n"
|
||||
" static print() {\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(obj) {\n"
|
||||
" writeObject_(obj)\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" return obj\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static printAll(sequence) {\n"
|
||||
" for (object in sequence) writeObject_(object)\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static write(obj) {\n"
|
||||
" writeObject_(obj)\n"
|
||||
" return obj\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static writeAll(sequence) {\n"
|
||||
" for (object in sequence) writeObject_(object)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static writeObject_(obj) {\n"
|
||||
" var string = obj.toString\n"
|
||||
" if (string is String) {\n"
|
||||
" writeString_(string)\n"
|
||||
" } else {\n"
|
||||
" writeString_(\"[invalid toString]\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class ClassAttributes {\n"
|
||||
" self { _attributes }\n"
|
||||
" methods { _methods }\n"
|
||||
" construct new(attributes, methods) {\n"
|
||||
" _attributes = attributes\n"
|
||||
" _methods = methods\n"
|
||||
" }\n"
|
||||
" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n"
|
||||
"}\n";
|
||||
@@ -0,0 +1,388 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "wren_debug.h"
|
||||
|
||||
void wrenDebugPrintStackTrace(WrenVM* vm)
|
||||
{
|
||||
// Bail if the host doesn't enable printing errors.
|
||||
if (vm->config.errorFn == NULL) return;
|
||||
|
||||
ObjFiber* fiber = vm->fiber;
|
||||
if (IS_STRING(fiber->error))
|
||||
{
|
||||
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
|
||||
NULL, -1, AS_CSTRING(fiber->error));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Print something a little useful here. Maybe the name of the error's
|
||||
// class?
|
||||
vm->config.errorFn(vm, WREN_ERROR_RUNTIME,
|
||||
NULL, -1, "[error object]");
|
||||
}
|
||||
|
||||
for (int i = fiber->numFrames - 1; i >= 0; i--)
|
||||
{
|
||||
CallFrame* frame = &fiber->frames[i];
|
||||
ObjFn* fn = frame->closure->fn;
|
||||
|
||||
// Skip over stub functions for calling methods from the C API.
|
||||
if (fn->module == NULL) continue;
|
||||
|
||||
// The built-in core module has no name. We explicitly omit it from stack
|
||||
// traces since we don't want to highlight to a user the implementation
|
||||
// detail of what part of the core module is written in C and what is Wren.
|
||||
if (fn->module->name == NULL) continue;
|
||||
|
||||
// -1 because IP has advanced past the instruction that it just executed.
|
||||
int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1];
|
||||
vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE,
|
||||
fn->module->name->value, line,
|
||||
fn->debug->name);
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpObject(Obj* obj)
|
||||
{
|
||||
switch (obj->type)
|
||||
{
|
||||
case OBJ_CLASS:
|
||||
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
|
||||
break;
|
||||
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
|
||||
case OBJ_FIBER: printf("[fiber %p]", obj); break;
|
||||
case OBJ_FN: printf("[fn %p]", obj); break;
|
||||
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
|
||||
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
|
||||
case OBJ_LIST: printf("[list %p]", obj); break;
|
||||
case OBJ_MAP: printf("[map %p]", obj); break;
|
||||
case OBJ_MODULE: printf("[module %p]", obj); break;
|
||||
case OBJ_RANGE: printf("[range %p]", obj); break;
|
||||
case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break;
|
||||
case OBJ_UPVALUE: printf("[upvalue %p]", obj); break;
|
||||
default: printf("[unknown object %d]", obj->type); break;
|
||||
}
|
||||
}
|
||||
|
||||
void wrenDumpValue(Value value)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
if (IS_NUM(value))
|
||||
{
|
||||
printf("%.14g", AS_NUM(value));
|
||||
}
|
||||
else if (IS_OBJ(value))
|
||||
{
|
||||
dumpObject(AS_OBJ(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (GET_TAG(value))
|
||||
{
|
||||
case TAG_FALSE: printf("false"); break;
|
||||
case TAG_NAN: printf("NaN"); break;
|
||||
case TAG_NULL: printf("null"); break;
|
||||
case TAG_TRUE: printf("true"); break;
|
||||
case TAG_UNDEFINED: UNREACHABLE();
|
||||
}
|
||||
}
|
||||
#else
|
||||
switch (value.type)
|
||||
{
|
||||
case VAL_FALSE: printf("false"); break;
|
||||
case VAL_NULL: printf("null"); break;
|
||||
case VAL_NUM: printf("%.14g", AS_NUM(value)); break;
|
||||
case VAL_TRUE: printf("true"); break;
|
||||
case VAL_OBJ: dumpObject(AS_OBJ(value)); break;
|
||||
case VAL_UNDEFINED: UNREACHABLE();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
{
|
||||
int start = i;
|
||||
uint8_t* bytecode = fn->code.data;
|
||||
Code code = (Code)bytecode[i];
|
||||
|
||||
int line = fn->debug->sourceLines.data[i];
|
||||
if (lastLine == NULL || *lastLine != line)
|
||||
{
|
||||
printf("%4d:", line);
|
||||
if (lastLine != NULL) *lastLine = line;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
printf(" %04d ", i++);
|
||||
|
||||
#define READ_BYTE() (bytecode[i++])
|
||||
#define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1])
|
||||
|
||||
#define BYTE_INSTRUCTION(name) \
|
||||
printf("%-16s %5d\n", name, READ_BYTE()); \
|
||||
break
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case CODE_CONSTANT:
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d '", "CONSTANT", constant);
|
||||
wrenDumpValue(fn->constants.data[constant]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_NULL: printf("NULL\n"); break;
|
||||
case CODE_FALSE: printf("FALSE\n"); break;
|
||||
case CODE_TRUE: printf("TRUE\n"); break;
|
||||
|
||||
case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break;
|
||||
case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break;
|
||||
case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break;
|
||||
case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break;
|
||||
case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break;
|
||||
case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break;
|
||||
case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break;
|
||||
case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break;
|
||||
case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break;
|
||||
|
||||
case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL");
|
||||
case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL");
|
||||
case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE");
|
||||
case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE");
|
||||
|
||||
case CODE_LOAD_MODULE_VAR:
|
||||
{
|
||||
int slot = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot,
|
||||
fn->module->variableNames.data[slot]->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_MODULE_VAR:
|
||||
{
|
||||
int slot = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot,
|
||||
fn->module->variableNames.data[slot]->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS");
|
||||
case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS");
|
||||
case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD");
|
||||
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
|
||||
|
||||
case CODE_POP: printf("POP\n"); break;
|
||||
|
||||
case CODE_CALL_0:
|
||||
case CODE_CALL_1:
|
||||
case CODE_CALL_2:
|
||||
case CODE_CALL_3:
|
||||
case CODE_CALL_4:
|
||||
case CODE_CALL_5:
|
||||
case CODE_CALL_6:
|
||||
case CODE_CALL_7:
|
||||
case CODE_CALL_8:
|
||||
case CODE_CALL_9:
|
||||
case CODE_CALL_10:
|
||||
case CODE_CALL_11:
|
||||
case CODE_CALL_12:
|
||||
case CODE_CALL_13:
|
||||
case CODE_CALL_14:
|
||||
case CODE_CALL_15:
|
||||
case CODE_CALL_16:
|
||||
{
|
||||
int numArgs = bytecode[i - 1] - CODE_CALL_0;
|
||||
int symbol = READ_SHORT();
|
||||
printf("CALL_%-11d %5d '%s'\n", numArgs, symbol,
|
||||
vm->methodNames.data[symbol]->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_SUPER_0:
|
||||
case CODE_SUPER_1:
|
||||
case CODE_SUPER_2:
|
||||
case CODE_SUPER_3:
|
||||
case CODE_SUPER_4:
|
||||
case CODE_SUPER_5:
|
||||
case CODE_SUPER_6:
|
||||
case CODE_SUPER_7:
|
||||
case CODE_SUPER_8:
|
||||
case CODE_SUPER_9:
|
||||
case CODE_SUPER_10:
|
||||
case CODE_SUPER_11:
|
||||
case CODE_SUPER_12:
|
||||
case CODE_SUPER_13:
|
||||
case CODE_SUPER_14:
|
||||
case CODE_SUPER_15:
|
||||
case CODE_SUPER_16:
|
||||
{
|
||||
int numArgs = bytecode[i - 1] - CODE_SUPER_0;
|
||||
int symbol = READ_SHORT();
|
||||
int superclass = READ_SHORT();
|
||||
printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol,
|
||||
vm->methodNames.data[symbol]->value, superclass);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "JUMP", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOOP:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "LOOP", offset, i - offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP_IF:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_AND:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "AND", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_OR:
|
||||
{
|
||||
int offset = READ_SHORT();
|
||||
printf("%-16s %5d to %d\n", "OR", offset, i + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break;
|
||||
case CODE_RETURN: printf("RETURN\n"); break;
|
||||
|
||||
case CODE_CLOSURE:
|
||||
{
|
||||
int constant = READ_SHORT();
|
||||
printf("%-16s %5d ", "CLOSURE", constant);
|
||||
wrenDumpValue(fn->constants.data[constant]);
|
||||
printf(" ");
|
||||
ObjFn* loadedFn = AS_FN(fn->constants.data[constant]);
|
||||
for (int j = 0; j < loadedFn->numUpvalues; j++)
|
||||
{
|
||||
int isLocal = READ_BYTE();
|
||||
int index = READ_BYTE();
|
||||
if (j > 0) printf(", ");
|
||||
printf("%s %d", isLocal ? "local" : "upvalue", index);
|
||||
}
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CONSTRUCT: printf("CONSTRUCT\n"); break;
|
||||
case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break;
|
||||
|
||||
case CODE_CLASS:
|
||||
{
|
||||
int numFields = READ_BYTE();
|
||||
printf("%-16s %5d fields\n", "CLASS", numFields);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
|
||||
case CODE_END_CLASS: printf("END_CLASS\n"); break;
|
||||
|
||||
case CODE_METHOD_INSTANCE:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol,
|
||||
vm->methodNames.data[symbol]->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_METHOD_STATIC:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol,
|
||||
vm->methodNames.data[symbol]->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_END_MODULE:
|
||||
printf("END_MODULE\n");
|
||||
break;
|
||||
|
||||
case CODE_IMPORT_MODULE:
|
||||
{
|
||||
int name = READ_SHORT();
|
||||
printf("%-16s %5d '", "IMPORT_MODULE", name);
|
||||
wrenDumpValue(fn->constants.data[name]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_IMPORT_VARIABLE:
|
||||
{
|
||||
int variable = READ_SHORT();
|
||||
printf("%-16s %5d '", "IMPORT_VARIABLE", variable);
|
||||
wrenDumpValue(fn->constants.data[variable]);
|
||||
printf("'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_END:
|
||||
printf("END\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("UKNOWN! [%d]\n", bytecode[i - 1]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Return how many bytes this instruction takes, or -1 if it's an END.
|
||||
if (code == CODE_END) return -1;
|
||||
return i - start;
|
||||
|
||||
#undef READ_BYTE
|
||||
#undef READ_SHORT
|
||||
}
|
||||
|
||||
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i)
|
||||
{
|
||||
return dumpInstruction(vm, fn, i, NULL);
|
||||
}
|
||||
|
||||
void wrenDumpCode(WrenVM* vm, ObjFn* fn)
|
||||
{
|
||||
printf("%s: %s\n",
|
||||
fn->module->name == NULL ? "<core>" : fn->module->name->value,
|
||||
fn->debug->name);
|
||||
|
||||
int i = 0;
|
||||
int lastLine = -1;
|
||||
for (;;)
|
||||
{
|
||||
int offset = dumpInstruction(vm, fn, i, &lastLine);
|
||||
if (offset == -1) break;
|
||||
i += offset;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void wrenDumpStack(ObjFiber* fiber)
|
||||
{
|
||||
printf("(fiber %p) ", fiber);
|
||||
for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++)
|
||||
{
|
||||
wrenDumpValue(*slot);
|
||||
printf(" | ");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef wren_debug_h
|
||||
#define wren_debug_h
|
||||
|
||||
#include "wren_value.h"
|
||||
#include "wren_vm.h"
|
||||
|
||||
// Prints the stack trace for the current fiber.
|
||||
//
|
||||
// Used when a fiber throws a runtime error which is not caught.
|
||||
void wrenDebugPrintStackTrace(WrenVM* vm);
|
||||
|
||||
// The "dump" functions are used for debugging Wren itself. Normal code paths
|
||||
// will not call them unless one of the various DEBUG_ flags is enabled.
|
||||
|
||||
// Prints a representation of [value] to stdout.
|
||||
void wrenDumpValue(Value value);
|
||||
|
||||
// Prints a representation of the bytecode for [fn] at instruction [i].
|
||||
int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i);
|
||||
|
||||
// Prints the disassembled code for [fn] to stdout.
|
||||
void wrenDumpCode(WrenVM* vm, ObjFn* fn);
|
||||
|
||||
// Prints the contents of the current stack for [fiber] to stdout.
|
||||
void wrenDumpStack(ObjFiber* fiber);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef wren_math_h
|
||||
#define wren_math_h
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// A union to let us reinterpret a double as raw bits and back.
|
||||
typedef union
|
||||
{
|
||||
uint64_t bits64;
|
||||
uint32_t bits32[2];
|
||||
double num;
|
||||
} WrenDoubleBits;
|
||||
|
||||
#define WREN_DOUBLE_QNAN_POS_MIN_BITS (UINT64_C(0x7FF8000000000000))
|
||||
#define WREN_DOUBLE_QNAN_POS_MAX_BITS (UINT64_C(0x7FFFFFFFFFFFFFFF))
|
||||
|
||||
#define WREN_DOUBLE_NAN (wrenDoubleFromBits(WREN_DOUBLE_QNAN_POS_MIN_BITS))
|
||||
|
||||
static inline double wrenDoubleFromBits(uint64_t bits)
|
||||
{
|
||||
WrenDoubleBits data;
|
||||
data.bits64 = bits;
|
||||
return data.num;
|
||||
}
|
||||
|
||||
static inline uint64_t wrenDoubleToBits(double num)
|
||||
{
|
||||
WrenDoubleBits data;
|
||||
data.num = num;
|
||||
return data.bits64;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,217 @@
|
||||
// This defines the bytecode instructions used by the VM. It does so by invoking
|
||||
// an OPCODE() macro which is expected to be defined at the point that this is
|
||||
// included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
|
||||
//
|
||||
// The first argument is the name of the opcode. The second is its "stack
|
||||
// effect" -- the amount that the op code changes the size of the stack. A
|
||||
// stack effect of 1 means it pushes a value and the stack grows one larger.
|
||||
// -2 means it pops two values, etc.
|
||||
//
|
||||
// Note that the order of instructions here affects the order of the dispatch
|
||||
// table in the VM's interpreter loop. That in turn affects caching which
|
||||
// affects overall performance. Take care to run benchmarks if you change the
|
||||
// order here.
|
||||
|
||||
// Load the constant at index [arg].
|
||||
OPCODE(CONSTANT, 1)
|
||||
|
||||
// Push null onto the stack.
|
||||
OPCODE(NULL, 1)
|
||||
|
||||
// Push false onto the stack.
|
||||
OPCODE(FALSE, 1)
|
||||
|
||||
// Push true onto the stack.
|
||||
OPCODE(TRUE, 1)
|
||||
|
||||
// Pushes the value in the given local slot.
|
||||
OPCODE(LOAD_LOCAL_0, 1)
|
||||
OPCODE(LOAD_LOCAL_1, 1)
|
||||
OPCODE(LOAD_LOCAL_2, 1)
|
||||
OPCODE(LOAD_LOCAL_3, 1)
|
||||
OPCODE(LOAD_LOCAL_4, 1)
|
||||
OPCODE(LOAD_LOCAL_5, 1)
|
||||
OPCODE(LOAD_LOCAL_6, 1)
|
||||
OPCODE(LOAD_LOCAL_7, 1)
|
||||
OPCODE(LOAD_LOCAL_8, 1)
|
||||
|
||||
// Note: The compiler assumes the following _STORE instructions always
|
||||
// immediately follow their corresponding _LOAD ones.
|
||||
|
||||
// Pushes the value in local slot [arg].
|
||||
OPCODE(LOAD_LOCAL, 1)
|
||||
|
||||
// Stores the top of stack in local slot [arg]. Does not pop it.
|
||||
OPCODE(STORE_LOCAL, 0)
|
||||
|
||||
// Pushes the value in upvalue [arg].
|
||||
OPCODE(LOAD_UPVALUE, 1)
|
||||
|
||||
// Stores the top of stack in upvalue [arg]. Does not pop it.
|
||||
OPCODE(STORE_UPVALUE, 0)
|
||||
|
||||
// Pushes the value of the top-level variable in slot [arg].
|
||||
OPCODE(LOAD_MODULE_VAR, 1)
|
||||
|
||||
// Stores the top of stack in top-level variable slot [arg]. Does not pop it.
|
||||
OPCODE(STORE_MODULE_VAR, 0)
|
||||
|
||||
// Pushes the value of the field in slot [arg] of the receiver of the current
|
||||
// function. This is used for regular field accesses on "this" directly in
|
||||
// methods. This instruction is faster than the more general CODE_LOAD_FIELD
|
||||
// instruction.
|
||||
OPCODE(LOAD_FIELD_THIS, 1)
|
||||
|
||||
// Stores the top of the stack in field slot [arg] in the receiver of the
|
||||
// current value. Does not pop the value. This instruction is faster than the
|
||||
// more general CODE_LOAD_FIELD instruction.
|
||||
OPCODE(STORE_FIELD_THIS, 0)
|
||||
|
||||
// Pops an instance and pushes the value of the field in slot [arg] of it.
|
||||
OPCODE(LOAD_FIELD, 0)
|
||||
|
||||
// Pops an instance and stores the subsequent top of stack in field slot
|
||||
// [arg] in it. Does not pop the value.
|
||||
OPCODE(STORE_FIELD, -1)
|
||||
|
||||
// Pop and discard the top of stack.
|
||||
OPCODE(POP, -1)
|
||||
|
||||
// Invoke the method with symbol [arg]. The number indicates the number of
|
||||
// arguments (not including the receiver).
|
||||
OPCODE(CALL_0, 0)
|
||||
OPCODE(CALL_1, -1)
|
||||
OPCODE(CALL_2, -2)
|
||||
OPCODE(CALL_3, -3)
|
||||
OPCODE(CALL_4, -4)
|
||||
OPCODE(CALL_5, -5)
|
||||
OPCODE(CALL_6, -6)
|
||||
OPCODE(CALL_7, -7)
|
||||
OPCODE(CALL_8, -8)
|
||||
OPCODE(CALL_9, -9)
|
||||
OPCODE(CALL_10, -10)
|
||||
OPCODE(CALL_11, -11)
|
||||
OPCODE(CALL_12, -12)
|
||||
OPCODE(CALL_13, -13)
|
||||
OPCODE(CALL_14, -14)
|
||||
OPCODE(CALL_15, -15)
|
||||
OPCODE(CALL_16, -16)
|
||||
|
||||
// Invoke a superclass method with symbol [arg]. The number indicates the
|
||||
// number of arguments (not including the receiver).
|
||||
OPCODE(SUPER_0, 0)
|
||||
OPCODE(SUPER_1, -1)
|
||||
OPCODE(SUPER_2, -2)
|
||||
OPCODE(SUPER_3, -3)
|
||||
OPCODE(SUPER_4, -4)
|
||||
OPCODE(SUPER_5, -5)
|
||||
OPCODE(SUPER_6, -6)
|
||||
OPCODE(SUPER_7, -7)
|
||||
OPCODE(SUPER_8, -8)
|
||||
OPCODE(SUPER_9, -9)
|
||||
OPCODE(SUPER_10, -10)
|
||||
OPCODE(SUPER_11, -11)
|
||||
OPCODE(SUPER_12, -12)
|
||||
OPCODE(SUPER_13, -13)
|
||||
OPCODE(SUPER_14, -14)
|
||||
OPCODE(SUPER_15, -15)
|
||||
OPCODE(SUPER_16, -16)
|
||||
|
||||
// Jump the instruction pointer [arg] forward.
|
||||
OPCODE(JUMP, 0)
|
||||
|
||||
// Jump the instruction pointer [arg] backward.
|
||||
OPCODE(LOOP, 0)
|
||||
|
||||
// Pop and if not truthy then jump the instruction pointer [arg] forward.
|
||||
OPCODE(JUMP_IF, -1)
|
||||
|
||||
// If the top of the stack is false, jump [arg] forward. Otherwise, pop and
|
||||
// continue.
|
||||
OPCODE(AND, -1)
|
||||
|
||||
// If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
|
||||
// and continue.
|
||||
OPCODE(OR, -1)
|
||||
|
||||
// Close the upvalue for the local on the top of the stack, then pop it.
|
||||
OPCODE(CLOSE_UPVALUE, -1)
|
||||
|
||||
// Exit from the current function and return the value on the top of the
|
||||
// stack.
|
||||
OPCODE(RETURN, 0)
|
||||
|
||||
// Creates a closure for the function stored at [arg] in the constant table.
|
||||
//
|
||||
// Following the function argument is a number of arguments, two for each
|
||||
// upvalue. The first is true if the variable being captured is a local (as
|
||||
// opposed to an upvalue), and the second is the index of the local or
|
||||
// upvalue being captured.
|
||||
//
|
||||
// Pushes the created closure.
|
||||
OPCODE(CLOSURE, 1)
|
||||
|
||||
// Creates a new instance of a class.
|
||||
//
|
||||
// Assumes the class object is in slot zero, and replaces it with the new
|
||||
// uninitialized instance of that class. This opcode is only emitted by the
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(CONSTRUCT, 0)
|
||||
|
||||
// Creates a new instance of a foreign class.
|
||||
//
|
||||
// Assumes the class object is in slot zero, and replaces it with the new
|
||||
// uninitialized instance of that class. This opcode is only emitted by the
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(FOREIGN_CONSTRUCT, 0)
|
||||
|
||||
// Creates a class. Top of stack is the superclass. Below that is a string for
|
||||
// the name of the class. Byte [arg] is the number of fields in the class.
|
||||
OPCODE(CLASS, -1)
|
||||
|
||||
// Ends a class.
|
||||
// Atm the stack contains the class and the ClassAttributes (or null).
|
||||
OPCODE(END_CLASS, -2)
|
||||
|
||||
// Creates a foreign class. Top of stack is the superclass. Below that is a
|
||||
// string for the name of the class.
|
||||
OPCODE(FOREIGN_CLASS, -1)
|
||||
|
||||
// Define a method for symbol [arg]. The class receiving the method is popped
|
||||
// off the stack, then the function defining the body is popped.
|
||||
//
|
||||
// If a foreign method is being defined, the "function" will be a string
|
||||
// identifying the foreign method. Otherwise, it will be a function or
|
||||
// closure.
|
||||
OPCODE(METHOD_INSTANCE, -2)
|
||||
|
||||
// Define a method for symbol [arg]. The class whose metaclass will receive
|
||||
// the method is popped off the stack, then the function defining the body is
|
||||
// popped.
|
||||
//
|
||||
// If a foreign method is being defined, the "function" will be a string
|
||||
// identifying the foreign method. Otherwise, it will be a function or
|
||||
// closure.
|
||||
OPCODE(METHOD_STATIC, -2)
|
||||
|
||||
// This is executed at the end of the module's body. Pushes NULL onto the stack
|
||||
// as the "return value" of the import statement and stores the module as the
|
||||
// most recently imported one.
|
||||
OPCODE(END_MODULE, 1)
|
||||
|
||||
// Import a module whose name is the string stored at [arg] in the constant
|
||||
// table.
|
||||
//
|
||||
// Pushes null onto the stack so that the fiber for the imported module can
|
||||
// replace that with a dummy value when it returns. (Fibers always return a
|
||||
// value when resuming a caller.)
|
||||
OPCODE(IMPORT_MODULE, 1)
|
||||
|
||||
// Import a variable from the most recently imported module. The name of the
|
||||
// variable to import is at [arg] in the constant table. Pushes the loaded
|
||||
// variable's value.
|
||||
OPCODE(IMPORT_VARIABLE, 1)
|
||||
|
||||
// This pseudo-instruction indicates the end of the bytecode. It should
|
||||
// always be preceded by a `CODE_RETURN`, so is never actually executed.
|
||||
OPCODE(END, 0)
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "wren_primitive.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
// Validates that [value] is an integer within `[0, count)`. Also allows
|
||||
// negative indices which map backwards from the end. Returns the valid positive
|
||||
// index value. If invalid, reports an error and returns `UINT32_MAX`.
|
||||
static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value,
|
||||
const char* argName)
|
||||
{
|
||||
if (!validateIntValue(vm, value, argName)) return UINT32_MAX;
|
||||
|
||||
// Negative indices count from the end.
|
||||
if (value < 0) value = count + value;
|
||||
|
||||
// Check bounds.
|
||||
if (value >= 0 && value < count) return (uint32_t)value;
|
||||
|
||||
vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
bool validateFn(WrenVM* vm, Value arg, const char* argName)
|
||||
{
|
||||
if (IS_CLOSURE(arg)) return true;
|
||||
RETURN_ERROR_FMT("$ must be a function.", argName);
|
||||
}
|
||||
|
||||
bool validateNum(WrenVM* vm, Value arg, const char* argName)
|
||||
{
|
||||
if (IS_NUM(arg)) return true;
|
||||
RETURN_ERROR_FMT("$ must be a number.", argName);
|
||||
}
|
||||
|
||||
bool validateIntValue(WrenVM* vm, double value, const char* argName)
|
||||
{
|
||||
if (trunc(value) == value) return true;
|
||||
RETURN_ERROR_FMT("$ must be an integer.", argName);
|
||||
}
|
||||
|
||||
bool validateInt(WrenVM* vm, Value arg, const char* argName)
|
||||
{
|
||||
// Make sure it's a number first.
|
||||
if (!validateNum(vm, arg, argName)) return false;
|
||||
return validateIntValue(vm, AS_NUM(arg), argName);
|
||||
}
|
||||
|
||||
bool validateKey(WrenVM* vm, Value arg)
|
||||
{
|
||||
if (wrenMapIsValidKey(arg)) return true;
|
||||
|
||||
RETURN_ERROR("Key must be a value type.");
|
||||
}
|
||||
|
||||
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
|
||||
const char* argName)
|
||||
{
|
||||
if (!validateNum(vm, arg, argName)) return UINT32_MAX;
|
||||
return validateIndexValue(vm, count, AS_NUM(arg), argName);
|
||||
}
|
||||
|
||||
bool validateString(WrenVM* vm, Value arg, const char* argName)
|
||||
{
|
||||
if (IS_STRING(arg)) return true;
|
||||
RETURN_ERROR_FMT("$ must be a string.", argName);
|
||||
}
|
||||
|
||||
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
|
||||
int* step)
|
||||
{
|
||||
*step = 0;
|
||||
|
||||
// Edge case: an empty range is allowed at the end of a sequence. This way,
|
||||
// list[0..-1] and list[0...list.count] can be used to copy a list even when
|
||||
// empty.
|
||||
if (range->from == *length &&
|
||||
range->to == (range->isInclusive ? -1.0 : (double)*length))
|
||||
{
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t from = validateIndexValue(vm, *length, range->from, "Range start");
|
||||
if (from == UINT32_MAX) return UINT32_MAX;
|
||||
|
||||
// Bounds check the end manually to handle exclusive ranges.
|
||||
double value = range->to;
|
||||
if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX;
|
||||
|
||||
// Negative indices count from the end.
|
||||
if (value < 0) value = *length + value;
|
||||
|
||||
// Convert the exclusive range to an inclusive one.
|
||||
if (!range->isInclusive)
|
||||
{
|
||||
// An exclusive range with the same start and end points is empty.
|
||||
if (value == from)
|
||||
{
|
||||
*length = 0;
|
||||
return from;
|
||||
}
|
||||
|
||||
// Shift the endpoint to make it inclusive, handling both increasing and
|
||||
// decreasing ranges.
|
||||
value += value >= from ? -1 : 1;
|
||||
}
|
||||
|
||||
// Check bounds.
|
||||
if (value < 0 || value >= *length)
|
||||
{
|
||||
vm->fiber->error = CONST_STRING(vm, "Range end out of bounds.");
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t to = (uint32_t)value;
|
||||
*length = abs((int)(from - to)) + 1;
|
||||
*step = from < to ? 1 : -1;
|
||||
return from;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#ifndef wren_primitive_h
|
||||
#define wren_primitive_h
|
||||
|
||||
#include "wren_vm.h"
|
||||
|
||||
// Binds a primitive method named [name] (in Wren) implemented using C function
|
||||
// [fn] to `ObjClass` [cls].
|
||||
#define PRIMITIVE(cls, name, function) \
|
||||
do \
|
||||
{ \
|
||||
int symbol = wrenSymbolTableEnsure(vm, \
|
||||
&vm->methodNames, name, strlen(name)); \
|
||||
Method method; \
|
||||
method.type = METHOD_PRIMITIVE; \
|
||||
method.as.primitive = prim_##function; \
|
||||
wrenBindMethod(vm, cls, symbol, method); \
|
||||
} while (false)
|
||||
|
||||
// Binds a primitive method named [name] (in Wren) implemented using C function
|
||||
// [fn] to `ObjClass` [cls], but as a FN call.
|
||||
#define FUNCTION_CALL(cls, name, function) \
|
||||
do \
|
||||
{ \
|
||||
int symbol = wrenSymbolTableEnsure(vm, \
|
||||
&vm->methodNames, name, strlen(name)); \
|
||||
Method method; \
|
||||
method.type = METHOD_FUNCTION_CALL; \
|
||||
method.as.primitive = prim_##function; \
|
||||
wrenBindMethod(vm, cls, symbol, method); \
|
||||
} while (false)
|
||||
|
||||
// Defines a primitive method whose C function name is [name]. This abstracts
|
||||
// the actual type signature of a primitive function and makes it clear which C
|
||||
// functions are invoked as primitives.
|
||||
#define DEF_PRIMITIVE(name) \
|
||||
static bool prim_##name(WrenVM* vm, Value* args)
|
||||
|
||||
#define RETURN_VAL(value) \
|
||||
do \
|
||||
{ \
|
||||
args[0] = value; \
|
||||
return true; \
|
||||
} while (false)
|
||||
|
||||
#define RETURN_OBJ(obj) RETURN_VAL(OBJ_VAL(obj))
|
||||
#define RETURN_BOOL(value) RETURN_VAL(BOOL_VAL(value))
|
||||
#define RETURN_FALSE RETURN_VAL(FALSE_VAL)
|
||||
#define RETURN_NULL RETURN_VAL(NULL_VAL)
|
||||
#define RETURN_NUM(value) RETURN_VAL(NUM_VAL(value))
|
||||
#define RETURN_TRUE RETURN_VAL(TRUE_VAL)
|
||||
|
||||
#define RETURN_ERROR(msg) \
|
||||
do \
|
||||
{ \
|
||||
vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \
|
||||
return false; \
|
||||
} while (false)
|
||||
|
||||
#define RETURN_ERROR_FMT(...) \
|
||||
do \
|
||||
{ \
|
||||
vm->fiber->error = wrenStringFormat(vm, __VA_ARGS__); \
|
||||
return false; \
|
||||
} while (false)
|
||||
|
||||
// Validates that the given [arg] is a function. Returns true if it is. If not,
|
||||
// reports an error and returns false.
|
||||
bool validateFn(WrenVM* vm, Value arg, const char* argName);
|
||||
|
||||
// Validates that the given [arg] is a Num. Returns true if it is. If not,
|
||||
// reports an error and returns false.
|
||||
bool validateNum(WrenVM* vm, Value arg, const char* argName);
|
||||
|
||||
// Validates that [value] is an integer. Returns true if it is. If not, reports
|
||||
// an error and returns false.
|
||||
bool validateIntValue(WrenVM* vm, double value, const char* argName);
|
||||
|
||||
// Validates that the given [arg] is an integer. Returns true if it is. If not,
|
||||
// reports an error and returns false.
|
||||
bool validateInt(WrenVM* vm, Value arg, const char* argName);
|
||||
|
||||
// Validates that [arg] is a valid object for use as a map key. Returns true if
|
||||
// it is. If not, reports an error and returns false.
|
||||
bool validateKey(WrenVM* vm, Value arg);
|
||||
|
||||
// Validates that the argument at [argIndex] is an integer within `[0, count)`.
|
||||
// Also allows negative indices which map backwards from the end. Returns the
|
||||
// valid positive index value. If invalid, reports an error and returns
|
||||
// `UINT32_MAX`.
|
||||
uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count,
|
||||
const char* argName);
|
||||
|
||||
// Validates that the given [arg] is a String. Returns true if it is. If not,
|
||||
// reports an error and returns false.
|
||||
bool validateString(WrenVM* vm, Value arg, const char* argName);
|
||||
|
||||
// Given a [range] and the [length] of the object being operated on, determines
|
||||
// the series of elements that should be chosen from the underlying object.
|
||||
// Handles ranges that count backwards from the end as well as negative ranges.
|
||||
//
|
||||
// Returns the index from which the range should start or `UINT32_MAX` if the
|
||||
// range is invalid. After calling, [length] will be updated with the number of
|
||||
// elements in the resulting sequence. [step] will be direction that the range
|
||||
// is going: `1` if the range is increasing from the start index or `-1` if the
|
||||
// range is decreasing.
|
||||
uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
|
||||
int* step);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "wren_utils.h"
|
||||
#include "wren_vm.h"
|
||||
|
||||
DEFINE_BUFFER(Byte, uint8_t);
|
||||
DEFINE_BUFFER(Int, int);
|
||||
DEFINE_BUFFER(String, ObjString*);
|
||||
|
||||
void wrenSymbolTableInit(SymbolTable* symbols)
|
||||
{
|
||||
wrenStringBufferInit(symbols);
|
||||
}
|
||||
|
||||
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols)
|
||||
{
|
||||
wrenStringBufferClear(vm, symbols);
|
||||
}
|
||||
|
||||
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length)
|
||||
{
|
||||
ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length));
|
||||
|
||||
wrenPushRoot(vm, &symbol->obj);
|
||||
wrenStringBufferWrite(vm, symbols, symbol);
|
||||
wrenPopRoot(vm);
|
||||
|
||||
return symbols->count - 1;
|
||||
}
|
||||
|
||||
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length)
|
||||
{
|
||||
// See if the symbol is already defined.
|
||||
int existing = wrenSymbolTableFind(symbols, name, length);
|
||||
if (existing != -1) return existing;
|
||||
|
||||
// New symbol, so add it.
|
||||
return wrenSymbolTableAdd(vm, symbols, name, length);
|
||||
}
|
||||
|
||||
int wrenSymbolTableFind(const SymbolTable* symbols,
|
||||
const char* name, size_t length)
|
||||
{
|
||||
// See if the symbol is already defined.
|
||||
// TODO: O(n). Do something better.
|
||||
for (int i = 0; i < symbols->count; i++)
|
||||
{
|
||||
if (wrenStringEqualsCString(symbols->data[i], name, length)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable)
|
||||
{
|
||||
for (int i = 0; i < symbolTable->count; i++)
|
||||
{
|
||||
wrenGrayObj(vm, &symbolTable->data[i]->obj);
|
||||
}
|
||||
|
||||
// Keep track of how much memory is still in use.
|
||||
vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data);
|
||||
}
|
||||
|
||||
int wrenUtf8EncodeNumBytes(int value)
|
||||
{
|
||||
ASSERT(value >= 0, "Cannot encode a negative value.");
|
||||
|
||||
if (value <= 0x7f) return 1;
|
||||
if (value <= 0x7ff) return 2;
|
||||
if (value <= 0xffff) return 3;
|
||||
if (value <= 0x10ffff) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wrenUtf8Encode(int value, uint8_t* bytes)
|
||||
{
|
||||
if (value <= 0x7f)
|
||||
{
|
||||
// Single byte (i.e. fits in ASCII).
|
||||
*bytes = value & 0x7f;
|
||||
return 1;
|
||||
}
|
||||
else if (value <= 0x7ff)
|
||||
{
|
||||
// Two byte sequence: 110xxxxx 10xxxxxx.
|
||||
*bytes = 0xc0 | ((value & 0x7c0) >> 6);
|
||||
bytes++;
|
||||
*bytes = 0x80 | (value & 0x3f);
|
||||
return 2;
|
||||
}
|
||||
else if (value <= 0xffff)
|
||||
{
|
||||
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
|
||||
*bytes = 0xe0 | ((value & 0xf000) >> 12);
|
||||
bytes++;
|
||||
*bytes = 0x80 | ((value & 0xfc0) >> 6);
|
||||
bytes++;
|
||||
*bytes = 0x80 | (value & 0x3f);
|
||||
return 3;
|
||||
}
|
||||
else if (value <= 0x10ffff)
|
||||
{
|
||||
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
|
||||
*bytes = 0xf0 | ((value & 0x1c0000) >> 18);
|
||||
bytes++;
|
||||
*bytes = 0x80 | ((value & 0x3f000) >> 12);
|
||||
bytes++;
|
||||
*bytes = 0x80 | ((value & 0xfc0) >> 6);
|
||||
bytes++;
|
||||
*bytes = 0x80 | (value & 0x3f);
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length)
|
||||
{
|
||||
// Single byte (i.e. fits in ASCII).
|
||||
if (*bytes <= 0x7f) return *bytes;
|
||||
|
||||
int value;
|
||||
uint32_t remainingBytes;
|
||||
if ((*bytes & 0xe0) == 0xc0)
|
||||
{
|
||||
// Two byte sequence: 110xxxxx 10xxxxxx.
|
||||
value = *bytes & 0x1f;
|
||||
remainingBytes = 1;
|
||||
}
|
||||
else if ((*bytes & 0xf0) == 0xe0)
|
||||
{
|
||||
// Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
|
||||
value = *bytes & 0x0f;
|
||||
remainingBytes = 2;
|
||||
}
|
||||
else if ((*bytes & 0xf8) == 0xf0)
|
||||
{
|
||||
// Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
|
||||
value = *bytes & 0x07;
|
||||
remainingBytes = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid UTF-8 sequence.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Don't read past the end of the buffer on truncated UTF-8.
|
||||
if (remainingBytes > length - 1) return -1;
|
||||
|
||||
while (remainingBytes > 0)
|
||||
{
|
||||
bytes++;
|
||||
remainingBytes--;
|
||||
|
||||
// Remaining bytes must be of form 10xxxxxx.
|
||||
if ((*bytes & 0xc0) != 0x80) return -1;
|
||||
|
||||
value = value << 6 | (*bytes & 0x3f);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int wrenUtf8DecodeNumBytes(uint8_t byte)
|
||||
{
|
||||
// If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so
|
||||
// don't count it at all.
|
||||
if ((byte & 0xc0) == 0x80) return 0;
|
||||
|
||||
// The first byte's high bits tell us how many bytes are in the UTF-8
|
||||
// sequence.
|
||||
if ((byte & 0xf8) == 0xf0) return 4;
|
||||
if ((byte & 0xf0) == 0xe0) return 3;
|
||||
if ((byte & 0xe0) == 0xc0) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
|
||||
int wrenPowerOf2Ceil(int n)
|
||||
{
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
n++;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t wrenValidateIndex(uint32_t count, int64_t value)
|
||||
{
|
||||
// Negative indices count from the end.
|
||||
if (value < 0) value = count + value;
|
||||
|
||||
// Check bounds.
|
||||
if (value >= 0 && value < count) return (uint32_t)value;
|
||||
|
||||
return UINT32_MAX;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#ifndef wren_utils_h
|
||||
#define wren_utils_h
|
||||
|
||||
#include "wren.h"
|
||||
#include "wren_common.h"
|
||||
|
||||
// Reusable data structures and other utility functions.
|
||||
|
||||
// Forward declare this here to break a cycle between wren_utils.h and
|
||||
// wren_value.h.
|
||||
typedef struct sObjString ObjString;
|
||||
|
||||
// We need buffers of a few different types. To avoid lots of casting between
|
||||
// void* and back, we'll use the preprocessor as a poor man's generics and let
|
||||
// it generate a few type-specific ones.
|
||||
#define DECLARE_BUFFER(name, type) \
|
||||
typedef struct \
|
||||
{ \
|
||||
type* data; \
|
||||
int count; \
|
||||
int capacity; \
|
||||
} name##Buffer; \
|
||||
void wren##name##BufferInit(name##Buffer* buffer); \
|
||||
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \
|
||||
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
|
||||
int count); \
|
||||
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data)
|
||||
|
||||
// This should be used once for each type instantiation, somewhere in a .c file.
|
||||
#define DEFINE_BUFFER(name, type) \
|
||||
void wren##name##BufferInit(name##Buffer* buffer) \
|
||||
{ \
|
||||
buffer->data = NULL; \
|
||||
buffer->capacity = 0; \
|
||||
buffer->count = 0; \
|
||||
} \
|
||||
\
|
||||
void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \
|
||||
{ \
|
||||
wrenReallocate(vm, buffer->data, 0, 0); \
|
||||
wren##name##BufferInit(buffer); \
|
||||
} \
|
||||
\
|
||||
void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \
|
||||
int count) \
|
||||
{ \
|
||||
if (buffer->capacity < buffer->count + count) \
|
||||
{ \
|
||||
int capacity = wrenPowerOf2Ceil(buffer->count + count); \
|
||||
buffer->data = (type*)wrenReallocate(vm, buffer->data, \
|
||||
buffer->capacity * sizeof(type), capacity * sizeof(type)); \
|
||||
buffer->capacity = capacity; \
|
||||
} \
|
||||
\
|
||||
for (int i = 0; i < count; i++) \
|
||||
{ \
|
||||
buffer->data[buffer->count++] = data; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \
|
||||
{ \
|
||||
wren##name##BufferFill(vm, buffer, data, 1); \
|
||||
}
|
||||
|
||||
DECLARE_BUFFER(Byte, uint8_t);
|
||||
DECLARE_BUFFER(Int, int);
|
||||
DECLARE_BUFFER(String, ObjString*);
|
||||
|
||||
// TODO: Change this to use a map.
|
||||
typedef StringBuffer SymbolTable;
|
||||
|
||||
// Initializes the symbol table.
|
||||
void wrenSymbolTableInit(SymbolTable* symbols);
|
||||
|
||||
// Frees all dynamically allocated memory used by the symbol table, but not the
|
||||
// SymbolTable itself.
|
||||
void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols);
|
||||
|
||||
// Adds name to the symbol table. Returns the index of it in the table.
|
||||
int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length);
|
||||
|
||||
// Adds name to the symbol table. Returns the index of it in the table. Will
|
||||
// use an existing symbol if already present.
|
||||
int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols,
|
||||
const char* name, size_t length);
|
||||
|
||||
// Looks up name in the symbol table. Returns its index if found or -1 if not.
|
||||
int wrenSymbolTableFind(const SymbolTable* symbols,
|
||||
const char* name, size_t length);
|
||||
|
||||
void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable);
|
||||
|
||||
// Returns the number of bytes needed to encode [value] in UTF-8.
|
||||
//
|
||||
// Returns 0 if [value] is too large to encode.
|
||||
int wrenUtf8EncodeNumBytes(int value);
|
||||
|
||||
// Encodes value as a series of bytes in [bytes], which is assumed to be large
|
||||
// enough to hold the encoded result.
|
||||
//
|
||||
// Returns the number of written bytes.
|
||||
int wrenUtf8Encode(int value, uint8_t* bytes);
|
||||
|
||||
// Decodes the UTF-8 sequence starting at [bytes] (which has max [length]),
|
||||
// returning the code point.
|
||||
//
|
||||
// Returns -1 if the bytes are not a valid UTF-8 sequence.
|
||||
int wrenUtf8Decode(const uint8_t* bytes, uint32_t length);
|
||||
|
||||
// Returns the number of bytes in the UTF-8 sequence starting with [byte].
|
||||
//
|
||||
// If the character at that index is not the beginning of a UTF-8 sequence,
|
||||
// returns 0.
|
||||
int wrenUtf8DecodeNumBytes(uint8_t byte);
|
||||
|
||||
// Returns the smallest power of two that is equal to or greater than [n].
|
||||
int wrenPowerOf2Ceil(int n);
|
||||
|
||||
// Validates that [value] is within `[0, count)`. Also allows
|
||||
// negative indices which map backwards from the end. Returns the valid positive
|
||||
// index value. If invalid, returns `UINT32_MAX`.
|
||||
uint32_t wrenValidateIndex(uint32_t count, int64_t value);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,890 @@
|
||||
#ifndef wren_value_h
|
||||
#define wren_value_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "wren_common.h"
|
||||
#include "wren_math.h"
|
||||
#include "wren_utils.h"
|
||||
|
||||
// This defines the built-in types and their core representations in memory.
|
||||
// Since Wren is dynamically typed, any variable can hold a value of any type,
|
||||
// and the type can change at runtime. Implementing this efficiently is
|
||||
// critical for performance.
|
||||
//
|
||||
// The main type exposed by this is [Value]. A C variable of that type is a
|
||||
// storage location that can hold any Wren value. The stack, module variables,
|
||||
// and instance fields are all implemented in C as variables of type Value.
|
||||
//
|
||||
// The built-in types for booleans, numbers, and null are unboxed: their value
|
||||
// is stored directly in the Value, and copying a Value copies the value. Other
|
||||
// types--classes, instances of classes, functions, lists, and strings--are all
|
||||
// reference types. They are stored on the heap and the Value just stores a
|
||||
// pointer to it. Copying the Value copies a reference to the same object. The
|
||||
// Wren implementation calls these "Obj", or objects, though to a user, all
|
||||
// values are objects.
|
||||
//
|
||||
// There is also a special singleton value "undefined". It is used internally
|
||||
// but never appears as a real value to a user. It has two uses:
|
||||
//
|
||||
// - It is used to identify module variables that have been implicitly declared
|
||||
// by use in a forward reference but not yet explicitly declared. These only
|
||||
// exist during compilation and do not appear at runtime.
|
||||
//
|
||||
// - It is used to represent unused map entries in an ObjMap.
|
||||
//
|
||||
// There are two supported Value representations. The main one uses a technique
|
||||
// called "NaN tagging" (explained in detail below) to store a number, any of
|
||||
// the value types, or a pointer, all inside one double-precision floating
|
||||
// point number. A larger, slower, Value type that uses a struct to store these
|
||||
// is also supported, and is useful for debugging the VM.
|
||||
//
|
||||
// The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
|
||||
// defined, Nan tagging is used.
|
||||
|
||||
// These macros cast a Value to one of the specific object types. These do *not*
|
||||
// perform any validation, so must only be used after the Value has been
|
||||
// ensured to be the right type.
|
||||
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) // ObjClass*
|
||||
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
|
||||
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
|
||||
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
|
||||
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
|
||||
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
|
||||
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
|
||||
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
|
||||
#define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule*
|
||||
#define AS_NUM(value) (wrenValueToNum(value)) // double
|
||||
#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange*
|
||||
#define AS_STRING(v) ((ObjString*)AS_OBJ(v)) // ObjString*
|
||||
#define AS_CSTRING(v) (AS_STRING(v)->value) // const char*
|
||||
|
||||
// These macros promote a primitive C value to a full Wren Value. There are
|
||||
// more defined below that are specific to the Nan tagged or other
|
||||
// representation.
|
||||
#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) // boolean
|
||||
#define NUM_VAL(num) (wrenNumToValue(num)) // double
|
||||
#define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj))) // Any Obj___*
|
||||
|
||||
// These perform type tests on a Value, returning `true` if the Value is of the
|
||||
// given type.
|
||||
#define IS_BOOL(value) (wrenIsBool(value)) // Bool
|
||||
#define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS)) // ObjClass
|
||||
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
|
||||
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
|
||||
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
|
||||
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
|
||||
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
|
||||
#define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList
|
||||
#define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap
|
||||
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
|
||||
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
|
||||
|
||||
// Creates a new string object from [text], which should be a bare C string
|
||||
// literal. This determines the length of the string automatically at compile
|
||||
// time based on the size of the character array (-1 for the terminating '\0').
|
||||
#define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1)
|
||||
|
||||
// Identifies which specific type a heap-allocated object is.
|
||||
typedef enum {
|
||||
OBJ_CLASS,
|
||||
OBJ_CLOSURE,
|
||||
OBJ_FIBER,
|
||||
OBJ_FN,
|
||||
OBJ_FOREIGN,
|
||||
OBJ_INSTANCE,
|
||||
OBJ_LIST,
|
||||
OBJ_MAP,
|
||||
OBJ_MODULE,
|
||||
OBJ_RANGE,
|
||||
OBJ_STRING,
|
||||
OBJ_UPVALUE
|
||||
} ObjType;
|
||||
|
||||
typedef struct sObjClass ObjClass;
|
||||
|
||||
// Base struct for all heap-allocated objects.
|
||||
typedef struct sObj Obj;
|
||||
struct sObj
|
||||
{
|
||||
ObjType type;
|
||||
bool isDark;
|
||||
|
||||
// The object's class.
|
||||
ObjClass* classObj;
|
||||
|
||||
// The next object in the linked list of all currently allocated objects.
|
||||
struct sObj* next;
|
||||
};
|
||||
|
||||
#if WREN_NAN_TAGGING
|
||||
|
||||
typedef uint64_t Value;
|
||||
|
||||
#else
|
||||
|
||||
typedef enum
|
||||
{
|
||||
VAL_FALSE,
|
||||
VAL_NULL,
|
||||
VAL_NUM,
|
||||
VAL_TRUE,
|
||||
VAL_UNDEFINED,
|
||||
VAL_OBJ
|
||||
} ValueType;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ValueType type;
|
||||
union
|
||||
{
|
||||
double num;
|
||||
Obj* obj;
|
||||
} as;
|
||||
} Value;
|
||||
|
||||
#endif
|
||||
|
||||
DECLARE_BUFFER(Value, Value);
|
||||
|
||||
// A heap-allocated string object.
|
||||
struct sObjString
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// Number of bytes in the string, not including the null terminator.
|
||||
uint32_t length;
|
||||
|
||||
// The hash value of the string's contents.
|
||||
uint32_t hash;
|
||||
|
||||
// Inline array of the string's bytes followed by a null terminator.
|
||||
char value[FLEXIBLE_ARRAY];
|
||||
};
|
||||
|
||||
// The dynamically allocated data structure for a variable that has been used
|
||||
// by a closure. Whenever a function accesses a variable declared in an
|
||||
// enclosing function, it will get to it through this.
|
||||
//
|
||||
// An upvalue can be either "closed" or "open". An open upvalue points directly
|
||||
// to a [Value] that is still stored on the fiber's stack because the local
|
||||
// variable is still in scope in the function where it's declared.
|
||||
//
|
||||
// When that local variable goes out of scope, the upvalue pointing to it will
|
||||
// be closed. When that happens, the value gets copied off the stack into the
|
||||
// upvalue itself. That way, it can have a longer lifetime than the stack
|
||||
// variable.
|
||||
typedef struct sObjUpvalue
|
||||
{
|
||||
// The object header. Note that upvalues have this because they are garbage
|
||||
// collected, but they are not first class Wren objects.
|
||||
Obj obj;
|
||||
|
||||
// Pointer to the variable this upvalue is referencing.
|
||||
Value* value;
|
||||
|
||||
// If the upvalue is closed (i.e. the local variable it was pointing to has
|
||||
// been popped off the stack) then the closed-over value will be hoisted out
|
||||
// of the stack into here. [value] will then be changed to point to this.
|
||||
Value closed;
|
||||
|
||||
// Open upvalues are stored in a linked list by the fiber. This points to the
|
||||
// next upvalue in that list.
|
||||
struct sObjUpvalue* next;
|
||||
} ObjUpvalue;
|
||||
|
||||
// The type of a primitive function.
|
||||
//
|
||||
// Primitives are similar to foreign functions, but have more direct access to
|
||||
// VM internals. It is passed the arguments in [args]. If it returns a value,
|
||||
// it places it in `args[0]` and returns `true`. If it causes a runtime error
|
||||
// or modifies the running fiber, it returns `false`.
|
||||
typedef bool (*Primitive)(WrenVM* vm, Value* args);
|
||||
|
||||
// TODO: See if it's actually a perf improvement to have this in a separate
|
||||
// struct instead of in ObjFn.
|
||||
// Stores debugging information for a function used for things like stack
|
||||
// traces.
|
||||
typedef struct
|
||||
{
|
||||
// The name of the function. Heap allocated and owned by the FnDebug.
|
||||
char* name;
|
||||
|
||||
// An array of line numbers. There is one element in this array for each
|
||||
// bytecode in the function's bytecode array. The value of that element is
|
||||
// the line in the source code that generated that instruction.
|
||||
IntBuffer sourceLines;
|
||||
} FnDebug;
|
||||
|
||||
// A loaded module and the top-level variables it defines.
|
||||
//
|
||||
// While this is an Obj and is managed by the GC, it never appears as a
|
||||
// first-class object in Wren.
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The currently defined top-level variables.
|
||||
ValueBuffer variables;
|
||||
|
||||
// Symbol table for the names of all module variables. Indexes here directly
|
||||
// correspond to entries in [variables].
|
||||
SymbolTable variableNames;
|
||||
|
||||
// The name of the module.
|
||||
ObjString* name;
|
||||
} ObjModule;
|
||||
|
||||
// A function object. It wraps and owns the bytecode and other debug information
|
||||
// for a callable chunk of code.
|
||||
//
|
||||
// Function objects are not passed around and invoked directly. Instead, they
|
||||
// are always referenced by an [ObjClosure] which is the real first-class
|
||||
// representation of a function. This isn't strictly necessary if they function
|
||||
// has no upvalues, but lets the rest of the VM assume all called objects will
|
||||
// be closures.
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
ByteBuffer code;
|
||||
ValueBuffer constants;
|
||||
|
||||
// The module where this function was defined.
|
||||
ObjModule* module;
|
||||
|
||||
// The maximum number of stack slots this function may use.
|
||||
int maxSlots;
|
||||
|
||||
// The number of upvalues this function closes over.
|
||||
int numUpvalues;
|
||||
|
||||
// The number of parameters this function expects. Used to ensure that .call
|
||||
// handles a mismatch between number of parameters and arguments. This will
|
||||
// only be set for fns, and not ObjFns that represent methods or scripts.
|
||||
int arity;
|
||||
FnDebug* debug;
|
||||
} ObjFn;
|
||||
|
||||
// An instance of a first-class function and the environment it has closed over.
|
||||
// Unlike [ObjFn], this has captured the upvalues that the function accesses.
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The function that this closure is an instance of.
|
||||
ObjFn* fn;
|
||||
|
||||
// The upvalues this function has closed over.
|
||||
ObjUpvalue* upvalues[FLEXIBLE_ARRAY];
|
||||
} ObjClosure;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Pointer to the current (really next-to-be-executed) instruction in the
|
||||
// function's bytecode.
|
||||
uint8_t* ip;
|
||||
|
||||
// The closure being executed.
|
||||
ObjClosure* closure;
|
||||
|
||||
// Pointer to the first stack slot used by this call frame. This will contain
|
||||
// the receiver, followed by the function's parameters, then local variables
|
||||
// and temporaries.
|
||||
Value* stackStart;
|
||||
} CallFrame;
|
||||
|
||||
// Tracks how this fiber has been invoked, aside from the ways that can be
|
||||
// detected from the state of other fields in the fiber.
|
||||
typedef enum
|
||||
{
|
||||
// The fiber is being run from another fiber using a call to `try()`.
|
||||
FIBER_TRY,
|
||||
|
||||
// The fiber was directly invoked by `runInterpreter()`. This means it's the
|
||||
// initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
|
||||
FIBER_ROOT,
|
||||
|
||||
// The fiber is invoked some other way. If [caller] is `NULL` then the fiber
|
||||
// was invoked using `call()`. If [numFrames] is zero, then the fiber has
|
||||
// finished running and is done. If [numFrames] is one and that frame's `ip`
|
||||
// points to the first byte of code, the fiber has not been started yet.
|
||||
FIBER_OTHER,
|
||||
} FiberState;
|
||||
|
||||
typedef struct sObjFiber
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The stack of value slots. This is used for holding local variables and
|
||||
// temporaries while the fiber is executing. It is heap-allocated and grown
|
||||
// as needed.
|
||||
Value* stack;
|
||||
|
||||
// A pointer to one past the top-most value on the stack.
|
||||
Value* stackTop;
|
||||
|
||||
// The number of allocated slots in the stack array.
|
||||
int stackCapacity;
|
||||
|
||||
// The stack of call frames. This is a dynamic array that grows as needed but
|
||||
// never shrinks.
|
||||
CallFrame* frames;
|
||||
|
||||
// The number of frames currently in use in [frames].
|
||||
int numFrames;
|
||||
|
||||
// The number of [frames] allocated.
|
||||
int frameCapacity;
|
||||
|
||||
// Pointer to the first node in the linked list of open upvalues that are
|
||||
// pointing to values still on the stack. The head of the list will be the
|
||||
// upvalue closest to the top of the stack, and then the list works downwards.
|
||||
ObjUpvalue* openUpvalues;
|
||||
|
||||
// The fiber that ran this one. If this fiber is yielded, control will resume
|
||||
// to this one. May be `NULL`.
|
||||
struct sObjFiber* caller;
|
||||
|
||||
// If the fiber failed because of a runtime error, this will contain the
|
||||
// error object. Otherwise, it will be null.
|
||||
Value error;
|
||||
|
||||
FiberState state;
|
||||
} ObjFiber;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// A primitive method implemented in C in the VM. Unlike foreign methods,
|
||||
// this can directly manipulate the fiber's stack.
|
||||
METHOD_PRIMITIVE,
|
||||
|
||||
// A primitive that handles .call on Fn.
|
||||
METHOD_FUNCTION_CALL,
|
||||
|
||||
// A externally-defined C method.
|
||||
METHOD_FOREIGN,
|
||||
|
||||
// A normal user-defined method.
|
||||
METHOD_BLOCK,
|
||||
|
||||
// No method for the given symbol.
|
||||
METHOD_NONE
|
||||
} MethodType;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MethodType type;
|
||||
|
||||
// The method function itself. The [type] determines which field of the union
|
||||
// is used.
|
||||
union
|
||||
{
|
||||
Primitive primitive;
|
||||
WrenForeignMethodFn foreign;
|
||||
ObjClosure* closure;
|
||||
} as;
|
||||
} Method;
|
||||
|
||||
DECLARE_BUFFER(Method, Method);
|
||||
|
||||
struct sObjClass
|
||||
{
|
||||
Obj obj;
|
||||
ObjClass* superclass;
|
||||
|
||||
// The number of fields needed for an instance of this class, including all
|
||||
// of its superclass fields.
|
||||
int numFields;
|
||||
|
||||
// The table of methods that are defined in or inherited by this class.
|
||||
// Methods are called by symbol, and the symbol directly maps to an index in
|
||||
// this table. This makes method calls fast at the expense of empty cells in
|
||||
// the list for methods the class doesn't support.
|
||||
//
|
||||
// You can think of it as a hash table that never has collisions but has a
|
||||
// really low load factor. Since methods are pretty small (just a type and a
|
||||
// pointer), this should be a worthwhile trade-off.
|
||||
MethodBuffer methods;
|
||||
|
||||
// The name of the class.
|
||||
ObjString* name;
|
||||
|
||||
// The ClassAttribute for the class, if any
|
||||
Value attributes;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
uint8_t data[FLEXIBLE_ARRAY];
|
||||
} ObjForeign;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
Value fields[FLEXIBLE_ARRAY];
|
||||
} ObjInstance;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The elements in the list.
|
||||
ValueBuffer elements;
|
||||
} ObjList;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The entry's key, or UNDEFINED_VAL if the entry is not in use.
|
||||
Value key;
|
||||
|
||||
// The value associated with the key. If the key is UNDEFINED_VAL, this will
|
||||
// be false to indicate an open available entry or true to indicate a
|
||||
// tombstone -- an entry that was previously in use but was then deleted.
|
||||
Value value;
|
||||
} MapEntry;
|
||||
|
||||
// A hash table mapping keys to values.
|
||||
//
|
||||
// We use something very simple: open addressing with linear probing. The hash
|
||||
// table is an array of entries. Each entry is a key-value pair. If the key is
|
||||
// the special UNDEFINED_VAL, it indicates no value is currently in that slot.
|
||||
// Otherwise, it's a valid key, and the value is the value associated with it.
|
||||
//
|
||||
// When entries are added, the array is dynamically scaled by GROW_FACTOR to
|
||||
// keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map
|
||||
// gets empty enough, it will be resized to a smaller array. When this happens,
|
||||
// all existing entries are rehashed and re-added to the new array.
|
||||
//
|
||||
// When an entry is removed, its slot is replaced with a "tombstone". This is an
|
||||
// entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing
|
||||
// for a key, we will continue past tombstones, because the desired key may be
|
||||
// found after them if the key that was removed was part of a prior collision.
|
||||
// When the array gets resized, all tombstones are discarded.
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The number of entries allocated.
|
||||
uint32_t capacity;
|
||||
|
||||
// The number of entries in the map.
|
||||
uint32_t count;
|
||||
|
||||
// Pointer to a contiguous array of [capacity] entries.
|
||||
MapEntry* entries;
|
||||
} ObjMap;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
|
||||
// The beginning of the range.
|
||||
double from;
|
||||
|
||||
// The end of the range. May be greater or less than [from].
|
||||
double to;
|
||||
|
||||
// True if [to] is included in the range.
|
||||
bool isInclusive;
|
||||
} ObjRange;
|
||||
|
||||
// An IEEE 754 double-precision float is a 64-bit value with bits laid out like:
|
||||
//
|
||||
// 1 Sign bit
|
||||
// | 11 Exponent bits
|
||||
// | | 52 Mantissa (i.e. fraction) bits
|
||||
// | | |
|
||||
// S[Exponent-][Mantissa------------------------------------------]
|
||||
//
|
||||
// The details of how these are used to represent numbers aren't really
|
||||
// relevant here as long we don't interfere with them. The important bit is NaN.
|
||||
//
|
||||
// An IEEE double can represent a few magical values like NaN ("not a number"),
|
||||
// Infinity, and -Infinity. A NaN is any value where all exponent bits are set:
|
||||
//
|
||||
// v--NaN bits
|
||||
// -11111111111----------------------------------------------------
|
||||
//
|
||||
// Here, "-" means "doesn't matter". Any bit sequence that matches the above is
|
||||
// a NaN. With all of those "-", it obvious there are a *lot* of different
|
||||
// bit patterns that all mean the same thing. NaN tagging takes advantage of
|
||||
// this. We'll use those available bit patterns to represent things other than
|
||||
// numbers without giving up any valid numeric values.
|
||||
//
|
||||
// NaN values come in two flavors: "signalling" and "quiet". The former are
|
||||
// intended to halt execution, while the latter just flow through arithmetic
|
||||
// operations silently. We want the latter. Quiet NaNs are indicated by setting
|
||||
// the highest mantissa bit:
|
||||
//
|
||||
// v--Highest mantissa bit
|
||||
// -[NaN ]1---------------------------------------------------
|
||||
//
|
||||
// If all of the NaN bits are set, it's not a number. Otherwise, it is.
|
||||
// That leaves all of the remaining bits as available for us to play with. We
|
||||
// stuff a few different kinds of things here: special singleton values like
|
||||
// "true", "false", and "null", and pointers to objects allocated on the heap.
|
||||
// We'll use the sign bit to distinguish singleton values from pointers. If
|
||||
// it's set, it's a pointer.
|
||||
//
|
||||
// v--Pointer or singleton?
|
||||
// S[NaN ]1---------------------------------------------------
|
||||
//
|
||||
// For singleton values, we just enumerate the different values. We'll use the
|
||||
// low bits of the mantissa for that, and only need a few:
|
||||
//
|
||||
// 3 Type bits--v
|
||||
// 0[NaN ]1------------------------------------------------[T]
|
||||
//
|
||||
// For pointers, we are left with 51 bits of mantissa to store an address.
|
||||
// That's more than enough room for a 32-bit address. Even 64-bit machines
|
||||
// only actually use 48 bits for addresses, so we've got plenty. We just stuff
|
||||
// the address right into the mantissa.
|
||||
//
|
||||
// Ta-da, double precision numbers, pointers, and a bunch of singleton values,
|
||||
// all stuffed into a single 64-bit sequence. Even better, we don't have to
|
||||
// do any masking or work to extract number values: they are unmodified. This
|
||||
// means math on numbers is fast.
|
||||
#if WREN_NAN_TAGGING
|
||||
|
||||
// A mask that selects the sign bit.
|
||||
#define SIGN_BIT ((uint64_t)1 << 63)
|
||||
|
||||
// The bits that must be set to indicate a quiet NaN.
|
||||
#define QNAN ((uint64_t)0x7ffc000000000000)
|
||||
|
||||
// If the NaN bits are set, it's not a number.
|
||||
#define IS_NUM(value) (((value) & QNAN) != QNAN)
|
||||
|
||||
// An object pointer is a NaN with a set sign bit.
|
||||
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
|
||||
|
||||
#define IS_FALSE(value) ((value) == FALSE_VAL)
|
||||
#define IS_NULL(value) ((value) == NULL_VAL)
|
||||
#define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL)
|
||||
|
||||
// Masks out the tag bits used to identify the singleton value.
|
||||
#define MASK_TAG (7)
|
||||
|
||||
// Tag values for the different singleton values.
|
||||
#define TAG_NAN (0)
|
||||
#define TAG_NULL (1)
|
||||
#define TAG_FALSE (2)
|
||||
#define TAG_TRUE (3)
|
||||
#define TAG_UNDEFINED (4)
|
||||
#define TAG_UNUSED2 (5)
|
||||
#define TAG_UNUSED3 (6)
|
||||
#define TAG_UNUSED4 (7)
|
||||
|
||||
// Value -> 0 or 1.
|
||||
#define AS_BOOL(value) ((value) == TRUE_VAL)
|
||||
|
||||
// Value -> Obj*.
|
||||
#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
|
||||
|
||||
// Singleton values.
|
||||
#define NULL_VAL ((Value)(uint64_t)(QNAN | TAG_NULL))
|
||||
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
|
||||
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
|
||||
#define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED))
|
||||
|
||||
// Gets the singleton type tag for a Value (which must be a singleton).
|
||||
#define GET_TAG(value) ((int)((value) & MASK_TAG))
|
||||
|
||||
#else
|
||||
|
||||
// Value -> 0 or 1.
|
||||
#define AS_BOOL(value) ((value).type == VAL_TRUE)
|
||||
|
||||
// Value -> Obj*.
|
||||
#define AS_OBJ(v) ((v).as.obj)
|
||||
|
||||
// Determines if [value] is a garbage-collected object or not.
|
||||
#define IS_OBJ(value) ((value).type == VAL_OBJ)
|
||||
|
||||
#define IS_FALSE(value) ((value).type == VAL_FALSE)
|
||||
#define IS_NULL(value) ((value).type == VAL_NULL)
|
||||
#define IS_NUM(value) ((value).type == VAL_NUM)
|
||||
#define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED)
|
||||
|
||||
// Singleton values.
|
||||
#define FALSE_VAL ((Value){ VAL_FALSE, { 0 } })
|
||||
#define NULL_VAL ((Value){ VAL_NULL, { 0 } })
|
||||
#define TRUE_VAL ((Value){ VAL_TRUE, { 0 } })
|
||||
#define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } })
|
||||
|
||||
#endif
|
||||
|
||||
// Creates a new "raw" class. It has no metaclass or superclass whatsoever.
|
||||
// This is only used for bootstrapping the initial Object and Class classes,
|
||||
// which are a little special.
|
||||
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
|
||||
|
||||
// Makes [superclass] the superclass of [subclass], and causes subclass to
|
||||
// inherit its methods. This should be called before any methods are defined
|
||||
// on subclass.
|
||||
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
|
||||
|
||||
// Creates a new class object as well as its associated metaclass.
|
||||
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
|
||||
ObjString* name);
|
||||
|
||||
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);
|
||||
|
||||
// Creates a new closure object that invokes [fn]. Allocates room for its
|
||||
// upvalues, but assumes outside code will populate it.
|
||||
ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
|
||||
|
||||
// Creates a new fiber object that will invoke [closure].
|
||||
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure);
|
||||
|
||||
// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
|
||||
// [stackStart].
|
||||
static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
||||
ObjClosure* closure, Value* stackStart)
|
||||
{
|
||||
// The caller should have ensured we already have enough capacity.
|
||||
ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame.");
|
||||
|
||||
CallFrame* frame = &fiber->frames[fiber->numFrames++];
|
||||
frame->stackStart = stackStart;
|
||||
frame->closure = closure;
|
||||
frame->ip = closure->fn->code.data;
|
||||
}
|
||||
|
||||
// Ensures [fiber]'s stack has at least [needed] slots.
|
||||
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed);
|
||||
|
||||
static inline bool wrenHasError(const ObjFiber* fiber)
|
||||
{
|
||||
return !IS_NULL(fiber->error);
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||
|
||||
// Creates a new empty function. Before being used, it must have code,
|
||||
// constants, etc. added to it.
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots);
|
||||
|
||||
void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length);
|
||||
|
||||
// Creates a new instance of the given [classObj].
|
||||
Value wrenNewInstance(WrenVM* vm, ObjClass* classObj);
|
||||
|
||||
// Creates a new list with [numElements] elements (which are left
|
||||
// uninitialized.)
|
||||
ObjList* wrenNewList(WrenVM* vm, uint32_t numElements);
|
||||
|
||||
// Inserts [value] in [list] at [index], shifting down the other elements.
|
||||
void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index);
|
||||
|
||||
// Removes and returns the item at [index] from [list].
|
||||
Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index);
|
||||
|
||||
// Searches for [value] in [list], returns the index or -1 if not found.
|
||||
int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value);
|
||||
|
||||
// Creates a new empty map.
|
||||
ObjMap* wrenNewMap(WrenVM* vm);
|
||||
|
||||
// Validates that [arg] is a valid object for use as a map key. Returns true if
|
||||
// it is and returns false otherwise. Use validateKey usually, for a runtime error.
|
||||
// This separation exists to aid the API in surfacing errors to the developer as well.
|
||||
static inline bool wrenMapIsValidKey(Value arg);
|
||||
|
||||
// Looks up [key] in [map]. If found, returns the value. Otherwise, returns
|
||||
// `UNDEFINED_VAL`.
|
||||
Value wrenMapGet(ObjMap* map, Value key);
|
||||
|
||||
// Associates [key] with [value] in [map].
|
||||
void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value);
|
||||
|
||||
void wrenMapClear(WrenVM* vm, ObjMap* map);
|
||||
|
||||
// Removes [key] from [map], if present. Returns the value for the key if found
|
||||
// or `NULL_VAL` otherwise.
|
||||
Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key);
|
||||
|
||||
// Creates a new module.
|
||||
ObjModule* wrenNewModule(WrenVM* vm, ObjString* name);
|
||||
|
||||
// Creates a new range from [from] to [to].
|
||||
Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive);
|
||||
|
||||
// Creates a new string object and copies [text] into it.
|
||||
//
|
||||
// [text] must be non-NULL.
|
||||
Value wrenNewString(WrenVM* vm, const char* text);
|
||||
|
||||
// Creates a new string object of [length] and copies [text] into it.
|
||||
//
|
||||
// [text] may be NULL if [length] is zero.
|
||||
Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length);
|
||||
|
||||
// Creates a new string object by taking a range of characters from [source].
|
||||
// The range starts at [start], contains [count] bytes, and increments by
|
||||
// [step].
|
||||
Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start,
|
||||
uint32_t count, int step);
|
||||
|
||||
// Produces a string representation of [value].
|
||||
Value wrenNumToString(WrenVM* vm, double value);
|
||||
|
||||
// Creates a new formatted string from [format] and any additional arguments
|
||||
// used in the format string.
|
||||
//
|
||||
// This is a very restricted flavor of formatting, intended only for internal
|
||||
// use by the VM. Two formatting characters are supported, each of which reads
|
||||
// the next argument as a certain type:
|
||||
//
|
||||
// $ - A C string.
|
||||
// @ - A Wren string object.
|
||||
Value wrenStringFormat(WrenVM* vm, const char* format, ...);
|
||||
|
||||
// Creates a new string containing the UTF-8 encoding of [value].
|
||||
Value wrenStringFromCodePoint(WrenVM* vm, int value);
|
||||
|
||||
// Creates a new string from the integer representation of a byte
|
||||
Value wrenStringFromByte(WrenVM* vm, uint8_t value);
|
||||
|
||||
// Creates a new string containing the code point in [string] starting at byte
|
||||
// [index]. If [index] points into the middle of a UTF-8 sequence, returns an
|
||||
// empty string.
|
||||
Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index);
|
||||
|
||||
// Search for the first occurence of [needle] within [haystack] and returns its
|
||||
// zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain
|
||||
// [needle].
|
||||
uint32_t wrenStringFind(ObjString* haystack, ObjString* needle,
|
||||
uint32_t startIndex);
|
||||
|
||||
// Returns true if [a] and [b] represent the same string.
|
||||
static inline bool wrenStringEqualsCString(const ObjString* a,
|
||||
const char* b, size_t length)
|
||||
{
|
||||
return a->length == length && memcmp(a->value, b, length) == 0;
|
||||
}
|
||||
|
||||
// Creates a new open upvalue pointing to [value] on the stack.
|
||||
ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value);
|
||||
|
||||
// Mark [obj] as reachable and still in use. This should only be called
|
||||
// during the sweep phase of a garbage collection.
|
||||
void wrenGrayObj(WrenVM* vm, Obj* obj);
|
||||
|
||||
// Mark [value] as reachable and still in use. This should only be called
|
||||
// during the sweep phase of a garbage collection.
|
||||
void wrenGrayValue(WrenVM* vm, Value value);
|
||||
|
||||
// Mark the values in [buffer] as reachable and still in use. This should only
|
||||
// be called during the sweep phase of a garbage collection.
|
||||
void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer);
|
||||
|
||||
// Processes every object in the gray stack until all reachable objects have
|
||||
// been marked. After that, all objects are either white (freeable) or black
|
||||
// (in use and fully traversed).
|
||||
void wrenBlackenObjects(WrenVM* vm);
|
||||
|
||||
// Releases all memory owned by [obj], including [obj] itself.
|
||||
void wrenFreeObj(WrenVM* vm, Obj* obj);
|
||||
|
||||
// Returns the class of [value].
|
||||
//
|
||||
// Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps
|
||||
// performance (significantly) in some cases, but degrades it in others. The
|
||||
// ones used by the implementation were chosen to give the best results in the
|
||||
// benchmarks.
|
||||
ObjClass* wrenGetClass(WrenVM* vm, Value value);
|
||||
|
||||
// Returns true if [a] and [b] are strictly the same value. This is identity
|
||||
// for object values, and value equality for unboxed values.
|
||||
static inline bool wrenValuesSame(Value a, Value b)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
// Value types have unique bit representations and we compare object types
|
||||
// by identity (i.e. pointer), so all we need to do is compare the bits.
|
||||
return a == b;
|
||||
#else
|
||||
if (a.type != b.type) return false;
|
||||
if (a.type == VAL_NUM) return a.as.num == b.as.num;
|
||||
return a.as.obj == b.as.obj;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns true if [a] and [b] are equivalent. Immutable values (null, bools,
|
||||
// numbers, ranges, and strings) are equal if they have the same data. All
|
||||
// other values are equal if they are identical objects.
|
||||
bool wrenValuesEqual(Value a, Value b);
|
||||
|
||||
// Returns true if [value] is a bool. Do not call this directly, instead use
|
||||
// [IS_BOOL].
|
||||
static inline bool wrenIsBool(Value value)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
return value == TRUE_VAL || value == FALSE_VAL;
|
||||
#else
|
||||
return value.type == VAL_FALSE || value.type == VAL_TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns true if [value] is an object of type [type]. Do not call this
|
||||
// directly, instead use the [IS___] macro for the type in question.
|
||||
static inline bool wrenIsObjType(Value value, ObjType type)
|
||||
{
|
||||
return IS_OBJ(value) && AS_OBJ(value)->type == type;
|
||||
}
|
||||
|
||||
// Converts the raw object pointer [obj] to a [Value].
|
||||
static inline Value wrenObjectToValue(Obj* obj)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
// The triple casting is necessary here to satisfy some compilers:
|
||||
// 1. (uintptr_t) Convert the pointer to a number of the right size.
|
||||
// 2. (uint64_t) Pad it up to 64 bits in 32-bit builds.
|
||||
// 3. Or in the bits to make a tagged Nan.
|
||||
// 4. Cast to a typedef'd value.
|
||||
return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj));
|
||||
#else
|
||||
Value value;
|
||||
value.type = VAL_OBJ;
|
||||
value.as.obj = obj;
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Interprets [value] as a [double].
|
||||
static inline double wrenValueToNum(Value value)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
return wrenDoubleFromBits(value);
|
||||
#else
|
||||
return value.as.num;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Converts [num] to a [Value].
|
||||
static inline Value wrenNumToValue(double num)
|
||||
{
|
||||
#if WREN_NAN_TAGGING
|
||||
return wrenDoubleToBits(num);
|
||||
#else
|
||||
Value value;
|
||||
value.type = VAL_NUM;
|
||||
value.as.num = num;
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool wrenMapIsValidKey(Value arg)
|
||||
{
|
||||
return IS_BOOL(arg)
|
||||
|| IS_CLASS(arg)
|
||||
|| IS_NULL(arg)
|
||||
|| IS_NUM(arg)
|
||||
|| IS_RANGE(arg)
|
||||
|| IS_STRING(arg);
|
||||
}
|
||||
|
||||
#endif
|
||||
+2000
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,251 @@
|
||||
#ifndef wren_vm_h
|
||||
#define wren_vm_h
|
||||
|
||||
#include "wren_common.h"
|
||||
#include "wren_compiler.h"
|
||||
#include "wren_value.h"
|
||||
#include "wren_utils.h"
|
||||
|
||||
// The maximum number of temporary objects that can be made visible to the GC
|
||||
// at one time.
|
||||
#define WREN_MAX_TEMP_ROOTS 8
|
||||
|
||||
typedef enum
|
||||
{
|
||||
#define OPCODE(name, _) CODE_##name,
|
||||
#include "wren_opcodes.h"
|
||||
#undef OPCODE
|
||||
} Code;
|
||||
|
||||
// A handle to a value, basically just a linked list of extra GC roots.
|
||||
//
|
||||
// Note that even non-heap-allocated values can be stored here.
|
||||
struct WrenHandle
|
||||
{
|
||||
Value value;
|
||||
|
||||
WrenHandle* prev;
|
||||
WrenHandle* next;
|
||||
};
|
||||
|
||||
struct WrenVM
|
||||
{
|
||||
ObjClass* boolClass;
|
||||
ObjClass* classClass;
|
||||
ObjClass* fiberClass;
|
||||
ObjClass* fnClass;
|
||||
ObjClass* listClass;
|
||||
ObjClass* mapClass;
|
||||
ObjClass* nullClass;
|
||||
ObjClass* numClass;
|
||||
ObjClass* objectClass;
|
||||
ObjClass* rangeClass;
|
||||
ObjClass* stringClass;
|
||||
|
||||
// The fiber that is currently running.
|
||||
ObjFiber* fiber;
|
||||
|
||||
// The loaded modules. Each key is an ObjString (except for the main module,
|
||||
// whose key is null) for the module's name and the value is the ObjModule
|
||||
// for the module.
|
||||
ObjMap* modules;
|
||||
|
||||
// The most recently imported module. More specifically, the module whose
|
||||
// code has most recently finished executing.
|
||||
//
|
||||
// Not treated like a GC root since the module is already in [modules].
|
||||
ObjModule* lastModule;
|
||||
|
||||
// Memory management data:
|
||||
|
||||
// The number of bytes that are known to be currently allocated. Includes all
|
||||
// memory that was proven live after the last GC, as well as any new bytes
|
||||
// that were allocated since then. Does *not* include bytes for objects that
|
||||
// were freed since the last GC.
|
||||
size_t bytesAllocated;
|
||||
|
||||
// The number of total allocated bytes that will trigger the next GC.
|
||||
size_t nextGC;
|
||||
|
||||
// The first object in the linked list of all currently allocated objects.
|
||||
Obj* first;
|
||||
|
||||
// The "gray" set for the garbage collector. This is the stack of unprocessed
|
||||
// objects while a garbage collection pass is in process.
|
||||
Obj** gray;
|
||||
int grayCount;
|
||||
int grayCapacity;
|
||||
|
||||
// The list of temporary roots. This is for temporary or new objects that are
|
||||
// not otherwise reachable but should not be collected.
|
||||
//
|
||||
// They are organized as a stack of pointers stored in this array. This
|
||||
// implies that temporary roots need to have stack semantics: only the most
|
||||
// recently pushed object can be released.
|
||||
Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
|
||||
|
||||
int numTempRoots;
|
||||
|
||||
// Pointer to the first node in the linked list of active handles or NULL if
|
||||
// there are none.
|
||||
WrenHandle* handles;
|
||||
|
||||
// Pointer to the bottom of the range of stack slots available for use from
|
||||
// the C API. During a foreign method, this will be in the stack of the fiber
|
||||
// that is executing a method.
|
||||
//
|
||||
// If not in a foreign method, this is initially NULL. If the user requests
|
||||
// slots by calling wrenEnsureSlots(), a stack is created and this is
|
||||
// initialized.
|
||||
Value* apiStack;
|
||||
|
||||
WrenConfiguration config;
|
||||
|
||||
// Compiler and debugger data:
|
||||
|
||||
// The compiler that is currently compiling code. This is used so that heap
|
||||
// allocated objects used by the compiler can be found if a GC is kicked off
|
||||
// in the middle of a compile.
|
||||
Compiler* compiler;
|
||||
|
||||
// There is a single global symbol table for all method names on all classes.
|
||||
// Method calls are dispatched directly by index in this table.
|
||||
SymbolTable methodNames;
|
||||
};
|
||||
|
||||
// A generic allocation function that handles all explicit memory management.
|
||||
// It's used like so:
|
||||
//
|
||||
// - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
|
||||
// return the allocated memory or NULL on failure.
|
||||
//
|
||||
// - To attempt to grow an existing allocation, [memory] is the memory,
|
||||
// [oldSize] is its previous size, and [newSize] is the desired size.
|
||||
// It should return [memory] if it was able to grow it in place, or a new
|
||||
// pointer if it had to move it.
|
||||
//
|
||||
// - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
|
||||
// but it will always return [memory].
|
||||
//
|
||||
// - To free memory, [memory] will be the memory to free and [newSize] and
|
||||
// [oldSize] will be zero. It should return NULL.
|
||||
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
|
||||
|
||||
// Invoke the finalizer for the foreign object referenced by [foreign].
|
||||
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
|
||||
|
||||
// Creates a new [WrenHandle] for [value].
|
||||
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value);
|
||||
|
||||
// Compile [source] in the context of [module] and wrap in a fiber that can
|
||||
// execute it.
|
||||
//
|
||||
// Returns NULL if a compile error occurred.
|
||||
ObjClosure* wrenCompileSource(WrenVM* vm, const char* module,
|
||||
const char* source, bool isExpression,
|
||||
bool printErrors);
|
||||
|
||||
// Looks up a variable from a previously-loaded module.
|
||||
//
|
||||
// Aborts the current fiber if the module or variable could not be found.
|
||||
Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName);
|
||||
|
||||
// Returns the value of the module-level variable named [name] in the main
|
||||
// module.
|
||||
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
|
||||
|
||||
// Adds a new implicitly declared top-level variable named [name] to [module]
|
||||
// based on a use site occurring on [line].
|
||||
//
|
||||
// Does not check to see if a variable with that name is already declared or
|
||||
// defined. Returns the symbol for the new variable or -2 if there are too many
|
||||
// variables defined.
|
||||
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
size_t length, int line);
|
||||
|
||||
// Adds a new top-level variable named [name] to [module], and optionally
|
||||
// populates line with the line of the implicit first use (line can be NULL).
|
||||
//
|
||||
// Returns the symbol for the new variable, -1 if a variable with the given name
|
||||
// is already defined, or -2 if there are too many variables defined.
|
||||
// Returns -3 if this is a top-level lowercase variable (localname) that was
|
||||
// used before being defined.
|
||||
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
size_t length, Value value, int* line);
|
||||
|
||||
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
|
||||
// arguments (including the receiver) to be on the top of the stack already.
|
||||
static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber,
|
||||
ObjClosure* closure, int numArgs)
|
||||
{
|
||||
// Grow the call frame array if needed.
|
||||
if (fiber->numFrames + 1 > fiber->frameCapacity)
|
||||
{
|
||||
int max = fiber->frameCapacity * 2;
|
||||
fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames,
|
||||
sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max);
|
||||
fiber->frameCapacity = max;
|
||||
}
|
||||
|
||||
// Grow the stack if needed.
|
||||
int stackSize = (int)(fiber->stackTop - fiber->stack);
|
||||
int needed = stackSize + closure->fn->maxSlots;
|
||||
wrenEnsureStack(vm, fiber, needed);
|
||||
|
||||
wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs);
|
||||
}
|
||||
|
||||
// Marks [obj] as a GC root so that it doesn't get collected.
|
||||
void wrenPushRoot(WrenVM* vm, Obj* obj);
|
||||
|
||||
// Removes the most recently pushed temporary root.
|
||||
void wrenPopRoot(WrenVM* vm);
|
||||
|
||||
// Returns the class of [value].
|
||||
//
|
||||
// Defined here instead of in wren_value.h because it's critical that this be
|
||||
// inlined. That means it must be defined in the header, but the wren_value.h
|
||||
// header doesn't have a full definitely of WrenVM yet.
|
||||
static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value)
|
||||
{
|
||||
if (IS_NUM(value)) return vm->numClass;
|
||||
if (IS_OBJ(value)) return AS_OBJ(value)->classObj;
|
||||
|
||||
#if WREN_NAN_TAGGING
|
||||
switch (GET_TAG(value))
|
||||
{
|
||||
case TAG_FALSE: return vm->boolClass; break;
|
||||
case TAG_NAN: return vm->numClass; break;
|
||||
case TAG_NULL: return vm->nullClass; break;
|
||||
case TAG_TRUE: return vm->boolClass; break;
|
||||
case TAG_UNDEFINED: UNREACHABLE();
|
||||
}
|
||||
#else
|
||||
switch (value.type)
|
||||
{
|
||||
case VAL_FALSE: return vm->boolClass;
|
||||
case VAL_NULL: return vm->nullClass;
|
||||
case VAL_NUM: return vm->numClass;
|
||||
case VAL_TRUE: return vm->boolClass;
|
||||
case VAL_OBJ: return AS_OBJ(value)->classObj;
|
||||
case VAL_UNDEFINED: UNREACHABLE();
|
||||
}
|
||||
#endif
|
||||
|
||||
UNREACHABLE();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns `true` if [name] is a local variable name (starts with a lowercase
|
||||
// letter).
|
||||
static inline bool wrenIsLocalName(const char* name)
|
||||
{
|
||||
return name[0] >= 'a' && name[0] <= 'z';
|
||||
}
|
||||
|
||||
static inline bool wrenIsFalsyValue(Value value)
|
||||
{
|
||||
return IS_FALSE(value) || IS_NULL(value);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user