diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index cdc8a658..7acc7308 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -70,7 +70,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2.0.0 - - run: sudo xcode-select -switch /Applications/Xcode_12.app/Contents/Developer + - run: sudo xcode-select -switch /Applications/Xcode_12.4.app/Contents/Developer - name: Cache dependancies uses: actions/cache@v2 with: diff --git a/.gitignore b/.gitignore index 282e8a46..c37dfc4a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,9 @@ libwren.a libwrend.a libadd.so **/.DS_Store -src/util/convert -src/util/embed +src/tools/convert +src/tools/embed src/modules/*.inc -include/wren.h include/SDL2/* lib/SDL2* lib/SDL/special @@ -36,4 +35,4 @@ dome.html dome.js dome.wasm -game.egg +*.egg diff --git a/.gitmodules b/.gitmodules index cf11bbc3..435b096b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "wren"] - path = lib/wren - url = https://github.com/wren-lang/wren.git [submodule "lib/SDL"] path = lib/SDL url = https://github.com/libsdl-org/SDL diff --git a/AUTHORS.md b/AUTHORS.md index 22b8963f..b80536f5 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -10,3 +10,5 @@ We'd like to thank the following people for their contributions. * Chayim Refael Friedman [https://github.com/ChayimFriedman2] * Benjamin Stigsen [https://github.com/benstigsen] * Alexandru Badiu, aka voidberg [https://github.com/voidberg] + * Aayush Kashyap [https://github.com/TheKing0x9] + * Trevor Martin [https://github.com/trelemar] diff --git a/Makefile b/Makefile index b610f860..bacfd7f5 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,11 @@ LIBS=lib OBJS=obj INCLUDES=include SOURCE_FILES = $(shell find src -type f) -UTILS = $(SOURCE)/util +TOOLS = $(SOURCE)/tools MODULES=$(SOURCE)/modules SCRIPTS=scripts +WREN_LIB ?= $(OBJS)/libwren.o +WREN_PARAMS ?= -DWREN_OPT_RANDOM=0 -DWREN_OPT_META=1 # Build flags @@ -19,13 +21,18 @@ MODE ?= release # ARCH = 64bit or 32bit UNAME_S = $(shell uname -s) UNAME_P = $(shell uname -p) +UNAME_M = $(shell uname -m) ifeq ($(UNAME_S), Darwin) SYSTEM ?= macosx ARCH ?= 64bit FRAMEWORK ?= $(shell which sdl2-config 1>/dev/null && echo "" || echo "framework") else ifeq ($(UNAME_S), Linux) SYSTEM ?= linux -ARCH ?= 64bit + ifeq ($(UNAME_M), aarch64) + ARCH ?= arm64 + else + ARCH ?= 64bit + endif else SYSTEM ?= windows ifneq (,$(findstring 32,$(UNAME_S))) @@ -41,6 +48,7 @@ TAGS = $(ARCH) $(SYSTEM) $(MODE) $(FRAMEWORK) $(SYMBOLS) ifneq ($(filter debug,$(TAGS)),) TAGS += symbols +WREN_PARAMS += -DDEBUG=1 endif OBJS := $(OBJS)/$(ARCH) @@ -74,6 +82,9 @@ ifdef DOME_OPT_VERSION else DOME_OPTS += -DDOME_VERSION=\"$(shell git describe --tags)\" endif +ifneq ($(filter windows,$(TAGS)),) + DOME_OPTS += -D__USE_MINGW_ANSI_STDIO=1 +endif SDL_CONFIG ?= $(shell which sdl2-config 1>/dev/null && echo "sdl2-config" || (which "$(LIBS)/sdl2-config" 1>/dev/null && echo "$(LIBS)/sdl2-config" || echo "")) @@ -91,14 +102,17 @@ endif CFLAGS = $(DOME_OPTS) -std=c99 -pedantic $(WARNING_FLAGS) -fvisibility=hidden -ifneq ($(filter macosx,$(TAGS)),) +ifneq ($(filter linux,$(TAGS)),) +CFLAGS += -D_XOPEN_SOURCE=500 +else ifneq ($(filter macosx,$(TAGS)),) CFLAGS += -mmacosx-version-min=10.12 +CFLAGS += -D_DARWIN_C_SOURCE endif ifneq ($(filter release,$(TAGS)),) -CFLAGS += -O3 +CFLAGS += -O2 else ifneq ($(filter debug,$(TAGS)),) -CFLAGS += -O0 +CFLAGS += -O0 ifneq ($(filter macosx,$(TAGS)),) CFLAGS += -fsanitize=address FFLAGS += -fsanitize=address @@ -132,11 +146,6 @@ SDLFLAGS=$(shell $(SDL_CONFIG) --libs) endif endif -ifneq ($(filter release,$(TAGS)),) -DEPS += -lwren -else ifneq ($(filter debug,$(TAGS)),) -DEPS += -lwrend -endif ifneq ($(and $(filter windows,$(TAGS)),$(filter static,$(TAGS))),) WINDOW_MODE ?= windows WINDOW_MODE_FLAG = -m$(WINDOW_MODE) @@ -147,7 +156,7 @@ FFLAGS += -F/Library/Frameworks -framework SDL2 endif LDFLAGS = -L$(LIBS) $(WINDOW_MODE_FLAG) $(SDLFLAGS) $(STATIC_FLAG) -ifneq ($(filter linux,$(TAGS)),) +ifneq ($(and $(filter linux,$(TAGS)), $(filter 64bit, $(TAGS))),) COMPAT_DEP = $(OBJS)/glibc_compat.o LDFLAGS += -Wl,--wrap=log,--wrap=log2,--wrap=exp,--wrap=pow,--wrap=expf,--wrap=powf,--wrap=logf endif @@ -157,25 +166,26 @@ LDFLAGS += $(DEPS) # Build Rules -PROJECTS := dome.bin +PROJECTS := dome.bin modules .PHONY: all clean reset cloc $(PROJECTS) all: $(PROJECTS) -WREN_LIB ?= $(LIBS)/libwren.a -WREN_PARAMS ?= $(ARCH) WREN_OPT_RANDOM=0 WREN_OPT_META=1 -$(LIBS)/wren/lib/libwren.a: - @echo "==== Cloning Wren ====" - git submodule update --init -- $(LIBS)/wren -$(LIBS)/wren: $(LIBS)/wren/lib/libwren.a -$(WREN_LIB): $(LIBS)/wren - @echo "==== Building Wren ====" - ./scripts/setup_wren.sh $(WREN_PARAMS) - -$(MODULES)/*.inc: $(UTILS)/embed.c $(MODULES)/*.wren +$(TOOLS)/embed: $(TOOLS)/embed-standalone.c $(TOOLS)/embedlib.c + @echo "==== Building standalone embed tool ====" + $(CC) -o $(TOOLS)/embed $(CFLAGS) $(TOOLS)/embed-standalone.c $(WINDOW_MODE_FLAG) + +$(MODULES)/*.inc: $(TOOLS)/embed $(MODULES)/*.wren @echo "==== Building DOME modules ====" ./scripts/generateEmbedModules.sh +modules: $(MODULES)/*.inc + +$(OBJS)/libwren.o: $(INCLUDES)/wren.c + @mkdir -p $(OBJS) + @echo "==== Building wren module ====" + $(CC) -c $(INCLUDES)/wren.c -o $(OBJS)/libwren.o $(WREN_PARAMS) $(IFLAGS) + $(OBJS)/glibc_compat.o: $(INCLUDES)/glibc_compat.c @mkdir -p $(OBJS) @echo "==== Building glibc_compat module ====" @@ -186,12 +196,12 @@ $(OBJS)/vendor.o: $(INCLUDES)/vendor.c @echo "==== Building vendor module ====" $(CC) $(CFLAGS) -c $(INCLUDES)/vendor.c -o $(OBJS)/vendor.o $(IFLAGS) -$(OBJS)/main.o: $(SOURCE_FILES) $(INCLUDES) $(WREN_LIB) $(MODULES)/*.inc +$(OBJS)/main.o: $(SOURCE_FILES) $(INCLUDES) $(MODULES)/*.inc @mkdir -p $(OBJS) @echo "==== Building core ($(TAGS)) module ====" $(CC) $(CFLAGS) -c $(SOURCE)/main.c -o $(OBJS)/main.o $(IFLAGS) -$(TARGET_NAME): $(OBJS)/main.o $(OBJS)/vendor.o $(COMPAT_DEP) $(WREN_LIB) +$(TARGET_NAME): $(OBJS)/main.o $(OBJS)/vendor.o $(OBJS)/libwren.o $(COMPAT_DEP) $(WREN_LIB) @echo "==== Linking DOME ($(TAGS)) ====" $(CC) $(CFLAGS) $(FFLAGS) -o $(TARGET_NAME) $(OBJS)/*.o $(ICON_OBJECT_FILE) $(LDFLAGS) ./scripts/set-executable-path.sh $(TARGET_NAME) @@ -201,7 +211,6 @@ $(OBJS): mkdir -p $(OBJS) $(OBJS)/wren.o: $(OBJS) - git submodule update --init -- $(LIBS)/wren ./scripts/setup_wren_web.sh # EMCC_FLAGS=--profiling -g @@ -218,9 +227,6 @@ clean: rm -rf $(OBJS)/*.o reset: git submodule foreach --recursive git clean -xfd - rm -rf $(LIBS)/libwren.a - rm -rf $(LIBS)/libwrend.a - rm -rf $(INCLUDES)/wren.h cloc: - cloc --by-file --force-lang="java",wren --fullpath --not-match-d "util" -not-match-f ".inc" src + cloc --by-file --force-lang="java",wren --fullpath --not-match-d "font" -not-match-f ".inc" src diff --git a/README.md b/README.md index 988d8a84..cfa63f05 100644 --- a/README.md +++ b/README.md @@ -82,16 +82,18 @@ var Game = Main.new() ## Modules -DOME provides the following modules/methods/classes: +DOME provides the following features, as more: - Graphics - Canvas - Rect - Point - Circle + - Ellipses - Lines + - Triangles - Color - - ImageData + - ImageData (aka Bitmap) - Draw sprites loaded from files (png) - Input - Keyboard @@ -100,16 +102,16 @@ DOME provides the following modules/methods/classes: - Filesystem - File reading and writing - Audio (stereo and mono OGG and WAV files only) +- Native Plugins (allowing access to all kinds of functionality!) ## TODO You can follow my progress on implementing DOME on [my twitter](https://twitter.com/avivbeeri/status/1012448692119457798). - Graphics - - Triangles + - Potential 3D rendering mode? - IO - Asynchronous Operations - - Audio and Graphics also - Network Access - UDP - HTTP client (maybe) @@ -119,8 +121,8 @@ You can follow my progress on implementing DOME on [my twitter](https://twitter. DOME currently depends on a few libraries to achieve it's functions. -- Wren (This is built by `make` automatically) -- SDL2 (version 2.0.12 or newer, please install separately when building DOME from source) +- Wren (included in the project repo already) +- SDL2 (version 2.0.12 or newer. If you install this from source, you'll want to build shared/dynamic libraries.) - utf8.h - stb_image - stb_image_write @@ -132,8 +134,9 @@ DOME currently depends on a few libraries to achieve it's functions. - tinydir - [ABC_fifo](https://github.com/avivbeeri/abc) (A SPMC threadpool/task dispatching FIFO I wrote for this project) - mkdirp +- whereami -Apart from SDL2, all other dependancies are baked in or linked statically. DOME aspires to be both minimalist and cross platform, so it depends on as few external components as possible. +Apart from SDL2, all other dependancies are baked in. DOME aspires to be both minimalist and cross platform, so it depends on as few external components as possible. ## Acknowledgements @@ -149,6 +152,7 @@ Apart from SDL2, all other dependancies are baked in or linked statically. DOME - cxong for [tinydir](https://github.com/cxong/tinydir) - Jon Olick for [jo_gif](https://www.jonolick.com/home/gif-writer) - Stephen Mathieson for [mkdirp](https://github.com/stephenmathieson/mkdirp.c) +- Gregory Pakosz for [whereami](https://github.com/gpakosz/whereami) ### Example Game Resources diff --git a/docs/guides/distribution.md b/docs/guides/distribution.md index 56dba4af..ea6b34af 100644 --- a/docs/guides/distribution.md +++ b/docs/guides/distribution.md @@ -1,53 +1,67 @@ [< Back](..) -Distributing your games +Distributing your applications =================== -DOME is designed to be cross-platform, and so the same Wren game files and assets should work across Windows, Mac and Linux. On Mac and Linux, the SDL2 shared library will need to be provided. +DOME is designed to be cross-platform, and so the same Wren code and assets should work across Windows, Mac and Linux. There are different approaches to sharing your application with others. + +Please note, none of the methods described here will provide adequate protection from reverse engineering or asset extraction, but may be a temporary hurdle against the casual user. They are intended only as measures of convenience. ## Basic Packaging -The easiest way to share a game you've made is to place a DOME binary, your source code and game assets into a single zip file. Do this once for each platform you wish to support, and share those zip files with your users. +The simplest and easiest way to share something you've made is to place a DOME binary, your source code and assets into a single zip file. Do this once for each platform you wish to support, and share those zip files with your users. If you are providing your own DOME binary, rather than a pre-compiled one, you will need to have access to the shared SDL2 library. + +## Bundling with `nest` -## NEST - Easy bundling +For convenience, and to reduce the risk of a file going missing, you can create a `.egg` bundle, which packages all of your application code and resources into a single file. This can be done using the `nest` tool built into DOME. -_(Please note: NEST is still in development, and has to be built from source)_ +To create a bundle, navigate to your application's root directory, before running the following on the commandline: + +``` +> dome nest [files | directories] +``` -For easy distribution, you can package all of your games resources into a single `.egg` file using a tool called [NEST](https://github.com/domeengine/nest). DOME automatically plays any file named `game.egg` in the current working directory. +This will bundle all the files and directories into a file named `game.egg`. DOME automatically plays any file named `game.egg` in the current working directory. It expects that bundles contain a `main.wren` file in the base directory of the bundle, as the entry point for execution. -If you use a `.egg` file, DOME expects your game to start from a `main.wren` in the base directory as it's entry point. +### Fused Mode -Install NEST, and then navigate to your main game directory, before running the following: +Depending on your needs, you might want to only distribute your application to your users as a single file. This is possible using DOME's "fuse" mode. Once you have a `.egg` file as described in the previous section, you can embed it inside a DOME executable using the "fuse" tool. Run DOME from the commandline like this: ``` -> nest -z -o game.egg -- [files | directories] +> dome fuse game.egg [destination file] ``` -## Cross-Platform Distribution -_(You can ignore this section if you are using pre-compiled DOME binaries.)_ +This creates a standalone executable which requires no other files to run. If you set a destination file, the resulting binary will be placed there. Otherwise, it'll be placed in the current working directory, as a file named `game` (or `game.exe` on Windows). This will only produce a binary for the current platform. You'll need to do this on each platform you want to distribute to. + +## Platform-specific Distribution Notes -This section discusses the needs of various platforms when distributing games with DOME. +This section discusses the needs of various platforms when distributing applications with DOME. ### Windows -On Windows, DOME comes compiled with all it's dependancies, so you just need to provide your game files. +On Windows, DOME comes compiled with all its dependancies, so you just need to provide your application files. -### Mac OS X +### Mac OS -On Mac, the SDL2 library needs to be provided. This can either be globally installed, or you can package it as part of an application bundle, using the following layout: +On Mac OS platforms, you can create an application bundle by arranging your code and assets into the following directory/file layout: + +`` is a placeholder and should be replaced by the name of your application. It must be named consistently in the bundle layout, as well as in the Info.plist file. ``` -+-- Game.app ++-- .app +-- Contents +-- Info.plist +-- MacOS +-- dome +-- libSDL2.dylib - +-- Game (This is a small runscript) + +-- (This is a small runscript) +-- Resources +-- game.egg +-- icon.icns + +-- other assets... ``` +You only need to provide libSDL2.dylib if you are not using a statically linked version of `dome`. (If you are using an official DOME binary, you don't need to worry.) + The runscript is very simple and looks like this: ```bash #!/bin/bash @@ -62,7 +76,7 @@ Finally, the Info.plist at minimum needs to look like this: CFBundleExecutable - Game + CFBundleIconFile icon NSHighResolutionCapable @@ -77,13 +91,14 @@ Doing this results in a self contained and easy to distribute application bundle ### Linux -To run your game on linux, make sure SDL2 is installed, then run the dome executable with a `game.egg` file in the same directory. +Running your application on Linux should be reasonably simple. If you use a version with a statically linked version of SDL2, then you can just run the `dome` executable with a `game.egg` file in the same directory. +If your binary is not statically linked, you'll need to acquire a copy of the SDL2 shared library, and either install it globally, or store a local copy with `dome`. ### Web -DOME has an experimental web engine, which you can use to play your game in a browser. +DOME has an experimental web engine, which you can use to run your application in a browser. To do this, you need to host the `dome.html` file and your `game.egg` file in the same directory on a server. -You may find that the performance of your game suffers when running in the browser, in which case it may not be suitable for playing in browsers. +You may find that the performance of your application suffers when running in the browser, in which case this may not be a suitable method of distribution. DOME's web engine does not currently support playing at full screen. diff --git a/docs/modules/dome.md b/docs/modules/dome.md index 1a4481b9..1e361c7c 100644 --- a/docs/modules/dome.md +++ b/docs/modules/dome.md @@ -26,6 +26,11 @@ Returns the integer number of seconds since the unix epoch (1st January 1970). ## Process ### Static Fields + +#### `static errorDialog : Boolean` + +Allows you to enable/disable error message boxes and is enabled by default. You can both get and set this value. + #### `static args: String[]` Returns a string list of all non-option command line arguments used to invoke DOME's execution. The first two elements of the returned list will be: diff --git a/docs/modules/graphics.md b/docs/modules/graphics.md index 0f204de7..099b6981 100644 --- a/docs/modules/graphics.md +++ b/docs/modules/graphics.md @@ -18,10 +18,14 @@ It contains the following classes: The `Canvas` class is the core api for graphical display. ### Fields +#### `static font: String` +This is the name of the default font used for `Canvas.print(str, x, y, color)`. You can set this to `Font.default` to return to the DOME built-in font. #### `static height: Number` This is the height of the canvas/viewport, in pixels. #### `static width: Number` This is the width of the canvas/viewport, in pixels. +#### `static offset : Vector` +A vector representing the Canvas offset. You can both get and set this value ### Methods #### `static circle(x: Number, y: Number, r: Number, c: Color) ` @@ -88,9 +92,11 @@ Resize the canvas to the given `width` and `height`, and reset the color of the If `c` isn't provided, we default to black. Resizing the canvas resets the "clipping region" to encompass the whole canvas, as if `Canvas.clip()` was called. -### Instance Field -#### `font: String` -This sets the name of the default font used for `Canvas.print(str, x, y, color)`. You can set this to `Font.default` to return to the DOME built-in font. +#### `static triangle(x0: Number, y0: Number, x1: Number, y1: Number, x2: Number, y2: Number, c: Color) ` +Draw a triangle with vertices at (_x0, y0_), (_x1, y1_), (_x2, y2_) in the color _c_. + +#### `static trianglefill(x0: Number, y0: Number, x1: Number, y1: Number, x2: Number, y2: Number, c: Color) ` +Draw a filled triangle with vertices at (_x0, y0_), (_x1, y1_), (_x2, y2_) in the color _c_. ## Color diff --git a/docs/modules/input.md b/docs/modules/input.md index 2e0b7b26..713405b1 100644 --- a/docs/modules/input.md +++ b/docs/modules/input.md @@ -88,6 +88,25 @@ This must be set while `handleText` is true, or the effect may be inconsistent. #### `static allPressed: Map` This returns a map containing the key names and corresponding `DigitalInput` objects, for all keys which are currently "down". +#### `static cursor: String` +Gets or sets the system cursor to the name provided. +Available cursor names are: +* `arrow` +* `ibeam` +* `wait` +* `crosshair` +* `waitarrow` +* `sizenwse` +* `sizenesw` +* `sizewe` +* `sizens` +* `sizeall` +* `no` +* `hand` + +Mac OS X will set the system cursor to `arrow` if `wait` or `waitarrow` is set. +Mac OS X will set the system cursor to a closed hand if `sizenwse`, `sizenesw` or `sizeall` is set. + #### `static hidden: Boolean` Controls whether the mouse cursor is shown or hidden. You can set and read from this field. diff --git a/docs/modules/math.md b/docs/modules/math.md index 5fa6a739..659dd40a 100644 --- a/docs/modules/math.md +++ b/docs/modules/math.md @@ -38,6 +38,9 @@ Returns the arctan of `n`. #### `ceil(n: Number): Number` Rounds `n` up to the next largest integer value. +#### `clamp(number : Number, min : Number, max : Number) : Number` +Clamps `number` between `min` and `max`. + #### `cos(n: Number): Number` Returns the cosine of `n`. diff --git a/docs/modules/random.md b/docs/modules/random.md index 63b363f6..2368fc79 100644 --- a/docs/modules/random.md +++ b/docs/modules/random.md @@ -5,7 +5,12 @@ random The `random` module provides utilities for generating pseudo-random numbers, for a variety of applications. Please note, this module should not be used for applications which require a cryptographically secure source of random numbers. -DOME's pseudo-random number generator is based on the "Squirrel3" noise function, described by [Squirrel Eiserloh](http://www.eiserloh.net/bio/) in [this talk](https://www.youtube.com/watch?v=LWFzPP8ZbdU). +DOME's provides two pseudo-random number generators - the "Squirrel3" noise function, described by [Squirrel Eiserloh](http://www.eiserloh.net/bio/) in [this talk](https://www.youtube.com/watch?v=LWFzPP8ZbdU), +and "Squirrel5" noise function, which is an [improvement](https://twitter.com/SquirrelTweets/status/1421251894274625536?s=20) +over the "Squirrel3" generator. + +The `Squirrel3` class, also exposed as `Random`, and the `Squirrel5` class both provide the same +API, as documented below. ## Random @@ -28,15 +33,15 @@ Creates a new instance of a random number generator, based on the provided seed Returns a floating point value in the range of `0.0...1.0`, inclusive of `0.0` but exclusive of `1.0`. #### `float(end: Number): Number` -Returns a floating point value in the range of `0.0...end``, inclusive of `0.0` but exclusive of `end`. +Returns a floating point value in the range of `0.0...end`, inclusive of `0.0` but exclusive of `end`. #### `float(start: Number, end: Number): Number` -Returns a floating point value in the range of `start...end``, inclusive of `start` but exclusive of `end`. +Returns a floating point value in the range of `start...end`, inclusive of `start` but exclusive of `end`. #### `int(end: Number): Number` -Returns an integer in the range `0.0...end`, inclusive of `0.0` but exclusive of `end`.` +Returns an integer in the range `0.0...end`, inclusive of `0.0` but exclusive of `end`. #### `int(start: Number, end: Number): Number` -Returns an integer in the range `start...end`, inclusive of `start` but exclusive of `end`.` +Returns an integer in the range `start...end`, inclusive of `start` but exclusive of `end`. #### `sample(list: List): Any` Given a `list`, this will pick an element from that list at random. @@ -46,4 +51,3 @@ Randomly selects `count` elements from the list and returns them in a new list. #### `shuffle(list: List): List` Uses the Fisher-Yates algorithm to shuffle the provided `list` in place. The list is also returned for convenience. - diff --git a/docs/plugins/audio.md b/docs/plugins/audio.md new file mode 100644 index 00000000..a2b9f31c --- /dev/null +++ b/docs/plugins/audio.md @@ -0,0 +1,102 @@ +[< Back](.) + +Audio +=============== + +This set of APIs gives you access to DOME's audio engine, to provide your own audio channel implementations. You can use this to synthesize sounds, or play custom audio formats. + + * [Acquisition](#acquistion) + * Enums + - [enum: CHANNEL_STATE](#enum-channel_state) + * Function Signatures + - [function: CHANNEL_mix](#function-channel_mix) + - [function: CHANNEL_callback](#function-channel_callback) + * Methods + - [method: channelCreate](#method-channelcreate) + - [method: getData](#method-getdata) + - [method: getState](#method-getstate) + - [method: setState](#method-setstate) + - [method: stop](#method-stop) + + +## Acquisition + +```c +AUDIO_API_v0* audio = (AUDIO_API_v0*)DOME_getAPI(API_AUDIO, AUDIO_API_VERSION); +``` + +## Enums + +### enum: CHANNEL_STATE + +Audio channels are enabled and disabled based on a state, which is represented by this enum. Supported states are the following: + +```c +enum CHANNEL_STATE { + CHANNEL_INITIALIZE, + CHANNEL_TO_PLAY, + CHANNEL_PLAYING, + CHANNEL_STOPPING, + CHANNEL_STOPPED +} +``` + +## Function Signatures + +### function: CHANNEL_mix +`CHANNEL_mix` functions have a signature of `void mix(CHANNEL_REF ref, float* buffer, size_t sampleRequestSize)`. + + * `ref` is a reference to the channel being mixed. + * `buffer` is an interleaved stereo buffer to write your audio data into. One sample is two values, for left and right, so `buffer` is `2 * sampleRequestSize` in size. + +This callback is called on DOME's Audio Engine mixer thread. It is essential that you avoid any slow operations (memory allocation, network) or you risk interruptions to the audio playback. + +### function: CHANNEL_callback +`CHANNEL_callback` functions have this signature: `void callback(CHANNEL_REF ref, WrenVM* vm)`. + + +## Methods + +### method: channelCreate +```c +CHANNEL_REF channelCreate(DOME_Context ctx, + CHANNEL_mix mix, + CHANNEL_callback update, + CHANNEL_callback finish, + void* userdata); +``` + +When you create a new audio channel, you must supply callbacks for mixing, updating and finalizing the channel. This allows it to play nicely within DOME's expected audio lifecycle. + +This method creates a channel with the specified callbacks and returns its corresponding CHANNEL_REF value, which can be used to manipulate the channel's state during execution. The channel will be created in the state `CHANNEL_INITIALIZE`, which gives you the opportunity to set up the channel configuration before it is played. + +The callbacks work like this: + - `update` is called once a frame, and can be used for safely modifying the state of the channel data. This callback holds a lock over the mixer thread, so avoid holding it for too long. + - `finish` is called once the channel has been set to `STOPPED`, before its memory is released. It is safe to expect that the channel will not be played again. + +The `userdata` is a pointer set by the plugin developer, which can be used to pass through associated data, and retrieved by [`getData(ref)`](#method-getdata). You are responsible for the management of the memory pointed to by that pointer and should avoid modifying the contents of the memory outside of the provided callbacks. + + +### method: getData +```c +void* getData(CHANNEL_REF ref) +``` +Fetch the `userdata` pointer for the given channel `ref`. + +### method: getState +```c +CHANNEL_STATE getState(CHANNEL_REF ref) +``` +Get the current [state](#enum-channel_state) of the channel specified by `ref`. + +### method: setState +```c +void setState(CHANNEL_REF ref, CHANNEL_STATE state) +``` +This allows you to specify the channel's [state](#enum-channel_state). DOME will only mix in channels in the following states: `CHANNEL_PLAYING` and `CHANNEL_STOPPING`. + +### method: stop +```c +void stop(CHANNEL_REF ref) +``` +Marks the audio channel as having stopped. This means that DOME will no longer play this channel. It will call the `finish` callback at it's next opportunity. diff --git a/docs/plugins/bitmap.md b/docs/plugins/bitmap.md new file mode 100644 index 00000000..670117b9 --- /dev/null +++ b/docs/plugins/bitmap.md @@ -0,0 +1,77 @@ +[< Back](.) + +Bitmap +=============== + +This set of APIs allows you to load and manage graphics from supported file formats. (See the graphics module for more information.) + + * [Acquisition](#acquistion) + * Struct + - [struct: DOME_Bitmap](#method-dome_bitmap) + * Methods + - [method: fromFile](#method-fromfile) + - [method: fromFileInMemory](#method-fromfileinmemory) + - [method: free](#method-free) + - [method: pget](#method-pget) + - [method: pset](#method-pset) + +## Acquisition + +```c +BITMAP_API_v0* bitmap = (BITMAP_API_v0*)DOME_getAPI(API_BITMAP, BITMAP_API_VERSION); +``` + +## Struct + +### DOME_Bitmap + +The DOME_Bitmap type contains the following fields: + +| Field | Type | Purpose | +| ---------------------------------------------------------------- | +| width | int32_t | Width of the bitmap in pixels. | +| height | int32_t | Height of the bitmap in pixels. | +| pixels | DOME_Color* | Pointer to the first pixel of the bitmap. | + +## Methods + +### method: fromFile +```c +DOME_Bitmap* fromFile(DOME_Context ctx, const char* path) +``` +Loads an image file from `path` on disk (relative to the application entry point) +and returns a `DOME_Bitmap`. You are responsible for freeing it using the `free` +function in this API. + +If there is a problem with the file, this method will return `NULL`. You can find out why +using `core->getLastError(DOME_Context ctx)`; + +### method: fromFileInMemory +```c +DOME_Bitmap* fromFileInMemory(DOME_Context ctx, void* buffer, size_t length) +``` +Loads an image file stored in memory at `buffer`, with a size of `length`, and +returns a `DOME_Bitmap`. You are responsible for freeing it using the `free` +function in this API. + +If there is a problem with the file, this method will return `NULL`. You can find out why +using `core->getLastError(DOME_Context ctx)`; + +### method: free +```c +void free(DOME_Bitmap* bitmap) +``` +Safely frees the `bitmap`. Make sure you don't attempt to use the `bitmap` pointer +after this function returns. + +### method: pget +```c +DOME_Color pget(DOME_Bitmap* bitmap, uint32_t x, uint32_t y) +``` +Returns the color of the pixel located at `(x, y)` in `bitmap`. + +### method: pset +```c +void pset(DOME_Bitmap* bitmap, uint32_t x, uint32_t y, DOME_Color color) +``` +Sets the color of the pixel located at `(x, y)` in `bitmap` to `color`. diff --git a/docs/plugins/canvas.md b/docs/plugins/canvas.md new file mode 100644 index 00000000..a43356aa --- /dev/null +++ b/docs/plugins/canvas.md @@ -0,0 +1,117 @@ +[< Back](.) + +Canvas +=============== + +This set of APIs allows you to modify what is displayed on the main canvas. +You can exploit this to allow for more efficient graphical rendering techniques. + +* [Acquisition](#acquistion) +* Enums + - [enum: DOME_DrawMode](#enum-dome_drawmode) +* Struct + - [struct: DOME_Color](#method-dome_color) +* Methods + - [method: draw](#method-draw) + - [method: getWidth](#method-getwidth) + - [method: getHeight](#method-getHeight) + - [method: line](#method-line) + - [method: pget](#method-pget) + - [method: pset](#method-pset) + - [method: rect](#method-rect) + - [method: rectfill](#method-rectfill) + - [method: unsafePset](#method-unsafepset) + +## Acquisition + +```c +CANVAS_API_v0* canvas = (CANVAS_API_v0*)DOME_getAPI(API_CANVAS, CANVAS_API_VERSION); +``` + +## Enums +### enum: DOME_DrawMode + +Some methods in this API allow you to enable or disable alpha-blending +for performance gains. + +```c +enum DOME_DrawMode { + DOME_DRAWMODE_BLEND +} +``` + +## Struct +### struct: DOME_Color + +The DOME_Bitmap type contains the following fields: + +| Field | Type | Purpose | +| ----------------------------------------------------------------------- | +| a | uint8_t | This is the color's alpha channel, from 0 - 255. | +| r | uint8_t | This is the color's red channel, from 0 - 255. | +| g | uint8_t | This is the color's green channel, from 0 - 255. | +| b | uint8_t | This is the color's blue channel, from 0 - 255. | + +This type is also a union. You can get all the fields simultaneously as a +32-bit integer, `value`, arranged in the layout `0xAARRGGBB`. + +## Methods +### method: draw +```c +void draw(DOME_Context ctx, DOME_Bitmap* bitmap, int32_t x, int32_t y, DOME_DRAWODE mode) +``` +Draws the `bitmap` to the canvas at `(x, y)`. If the `mode` is set, alpha-blending +will be applied. This will ignore the canvas draw context (offset, clipping region, etc). + +### method: getWidth +```c +uint32_t getWidth(DOME_Context ctx) +``` +Returns the width of the canvas, in pixels. + +### method: getHeight +```c +uint32_t getHeight(DOME_Context ctx) +``` +Returns the height of the canvas, in pixels. + +### method: line +```c +void line(DOME_Context ctx, int64_t x0, int64_t y0, int64_t x1, int64_t y1, DOME_Color color); +``` +Draws a one pixel wide line between `(x0, y0)` and `(x1, y1)`, in the chosen `color`. + +### method: pget +```c +DOME_Color pget(DOME_Context ctx, uint32_t x, uint32_t y) +``` +Returns the color of the pixel located at `(x, y)` in the canvas. + +### method: pset +```c +void pset(DOME_Context ctx, uint32_t x, uint32_t y, DOME_Color color) +``` +Sets the color of the pixel located at `(x, y)` in the canvas to `color`. + +### method: rect +```c +void rect(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color); +``` + +Draws a rectangle (edges only) with size `(width, height)`, with it's top-left corner at `(x, y)`. + +### method: rectfill +```c +void rectfill(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color); +``` + +Draws a filled rectangle with size `(width, height)`, with it's top-left corner at `(x, y)`. + +### method: unsafePset +```c +void unsafePset(DOME_Context ctx, uint32_t x, uint32_t y, DOME_Color color) +``` +Sets the color of the pixel located at `(x, y)` in the canvas to `color`. +This function is provided for performance-sensitive applications. +It does not do range checks. If you attempt to set a pixel outside the canvas, +you risk crashing DOME. diff --git a/docs/plugins/core.md b/docs/plugins/core.md new file mode 100644 index 00000000..a3b9a2dc --- /dev/null +++ b/docs/plugins/core.md @@ -0,0 +1,121 @@ +[< Back](.) + +Core +=============== + +This API allows your plugin to register modules and provides some basic utilities. + + * Enums + - [enum: DOME_Result](#enum-dome_result) + * Function Signatures + - [function: DOME_ForeignFn](#function-dome_foreignfn) + - [function: DOME_FinalizerFn](#function-dome_finalizerfn) + * Methods + - [method: registerModule](#method-registermodule) + - [method: registerClass](#method-registerclass) + - [method: registerFn](#method-registerfn) + - [method: lockModule](#method-lockmodule) + - [method: getContext](#method-getcontext) + - [method: getLastError](#method-getlasterror) + - [method: log](#method-log) + + +### Acquisition + +```c +DOME_API_v0* core = (DOME_API_v0*)DOME_getAPI(API_DOME, DOME_API_VERSION); +``` + +### Enums: + +#### enum: DOME_Result + +Various methods return an enum of type `DOME_Result`, which indicates success or failure. These are the valid values: + + * `DOME_RESULT_SUCCESS` + * `DOME_RESULT_FAILURE` + * `DOME_RESULT_UNKNOWN` + +### Function signatures + +#### function: DOME_ForeignFn +`DOME_ForeignFn` methods have the signature: `void method(WrenVM* vm)` to match the `WrenForeignMethodFn` type. + +#### function: DOME_FinalizerFn +`DOME_FinalizerFn` methods have the signature: `void finalize(void* vm)`, to match the `WrenFinalizerFn` type. + +### Methods + +#### method: registerModule +```c +DOME_Result registerModule(DOME_Context ctx, + const char* name, + const char* moduleSource) +``` +This call registers module `name` with the source code `moduleSource`. You cannot register modules with the same name as DOME's internal modules. These are reserved. +DOME creates a copy of the `name` and `moduleSource`, so you are able to free the pointers if necessary. +Returns `DOME_RESULT_SUCCESS` if the module was successfully registered, and `DOME_RESULT_FAILURE` otherwise. + +#### method: registerClass +```c +DOME_Result registerClass(DOME_Context ctx, + const char* moduleName, + const char* className, + DOME_ForeignFn allocate, + DOME_FinalizerFn finalize) +``` +Register the `allocate` and `finalize` methods for `className` in `moduleName`, so that instances of the foreign class can be allocated, and optionally finalized. +The `finalize` method is your final chance to deal with the userdata attached to your foreign class. You won't have VM access inside this method. +DOME creates a copy of the `className`, so you are able to free the pointer if necessary. + +Returns `DOME_RESULT_SUCCESS` if the class is registered and `DOME_RESULT_FAILURE` otherwise. Failure will occur if `allocate` method is provided. The `finalize` argument can optionally be `NULL`. + + +#### method: registerFn +```c +DOME_Result registerFn(DOME_Context ctx, + const char* name, + const char* signature, + DOME_ForeignFn method) +``` +Register `method` as the function to call for the foreign method specified by `signature` in the module `name`. +DOME creates a copy of the `signature`, so you are able to free the pointer if necessary. +Returns `DOME_RESULT_SUCCESS` if the function was successfully registered, and `DOME_RESULT_FAILURE` otherwise. + +The format for the `signature` string is as follows: + * `static` if the method is a static class method, followed by a space, otherwise both are omitted. + * `ClassName` for the class method being declared, followed by a period (`.`) + * `methodName` which is the name of the field/method being exposed. + - If this is a field getter, nothing else is needed. + - If this is a field setter, add `=(_)` + - If this is a method, then parenthesis and a comma seperated list of underscores (`_`) follow, for the number of arguments the method takes. + - You can also use the setter and getter syntax for the class' subscript operator `[]`, which can be defined with one or more parameters. + - Wren methods can have up to 16 arguments, and are overloaded by arity. For example, `Test.do(_)` is considered different to `Test.do(_,_)` and so on. + +#### method: lockModule +```c +void lockModule(DOME_Context ctx, const char* name) +``` +This marks the module `name` as locked, so that further functions cannot modify it. It is recommended to do this after you have registered all the methods for your module, however there is no requirement to. + +#### method: getContext +```c +DOME_Context getContext(WrenVM* vm) +``` +This allows foreign functions called by the Wren VM to access the current DOME context, to call various APIs. + +#### method: getLastError +```c +DOME_Context getLastError(WrenVM* vm) +``` +This returns the last error message reported by a failed plugin API call. +The error message will never be longer than 4096 bytes, including a terminating character. + +#### method: log +```c +void log(DOME_Context ctx, const char* text, ...) +``` + +Using this method allows for formatted output of `text` to the various debugging outputs DOME uses (stdout, a debug console on Windows and a DOME-log.txt file). + +You can use C-style specifiers for the `text` string, as used in the `printf` family of functions. diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 57d19678..a736249d 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -21,33 +21,13 @@ Advanced developers are invited to build native plugins using a compiled languag - [Post-Draw](#post-draw) - [Shutdown](#shutdown) * API Services - - [Core](#core) - * Enums - - [enum: DOME_Result](#enum-dome_result) - * Function Signatures - - [function: DOME_ForeignFn](#function-dome_foreignfn) - - [function: DOME_FinalizerFn](#function-dome_finalizerfn) - * Methods - - [method: registerModule](#method-registermodule) - - [method: registerClass](#method-registerclass) - - [method: registerFn](#method-registerfn) - - [method: lockModule](#method-lockmodule) - - [method: getContext](#method-getcontext) - - [method: log](#method-log) - - [Wren](#wren) - * [Module Embedding](#module-embedding) - - [Audio](#audio) - * Enums - - [enum: CHANNEL_STATE](#enum-channel_state) - * Function Signatures - - [function: CHANNEL_mix](#function-channel_mix) - - [function: CHANNEL_callback](#function-channel_callback) - * Methods - - [method: channelCreate](#method-channelcreate) - - [method: getData](#method-getdata) - - [method: getState](#method-getstate) - - [method: setState](#method-setstate) - - [method: stop](#method-stop) + - [Core](core) + - [Wren](wren) + - [Audio](audio) + - [Bitmap](bitmap) + - [Canvas](canvas) + - [I/O](io) + # Getting Started @@ -123,7 +103,6 @@ DOME_Result PLUGIN_onShutdown(DOME_Context ctx) This hook occurs when the plugin is being unloaded, usually because DOME is in the process of quitting. This is your last opportunity to free any resources your plugin is holding on to, and cleaning up any other background processes. - # API Services The DOME Plugin API is split into different pieces, divided by general purpose and version. This is to allow maximum backwards-compatibility as new features are added. @@ -134,260 +113,13 @@ APIs are provided as a struct of function pointers, returned from: void* DOME_getAPI(API_TYPE type, int API_VERSION) ``` -## Core - -This API allows your plugin to register modules and provides some basic utilities. - -### Acquisition - -```c -DOME_API_v0* core = (DOME_API_v0*)DOME_getAPI(API_DOME, DOME_API_VERSION); -``` - -### Enums: - -#### enum: DOME_Result - -Various methods return an enum of type `DOME_Result`, which indicates success or failure. These are the valid values: - - * `DOME_RESULT_SUCCESS` - * `DOME_RESULT_FAILURE` - * `DOME_RESULT_UNKNOWN` - -### Function signatures - -#### function: DOME_ForeignFn -`DOME_ForeignFn` methods have the signature: `void method(WrenVM* vm)` to match the `WrenForeignMethodFn` type. - -#### function: DOME_FinalizerFn -`DOME_FinalizerFn` methods have the signature: `void finalize(void* vm)`, to match the `WrenFinalizerFn` type. - -### Methods - -#### method: registerModule -```c -DOME_Result registerModule(DOME_Context ctx, - const char* name, - const char* moduleSource) -``` -This call registers module `name` with the source code `moduleSource`. You cannot register modules with the same name as DOME's internal modules. These are reserved. -DOME creates a copy of the `name` and `moduleSource`, so you are able to free the pointers if necessary. -Returns `DOME_RESULT_SUCCESS` if the module was successfully registered, and `DOME_RESULT_FAILURE` otherwise. - -#### method: registerClass -```c -DOME_Result registerClass(DOME_Context ctx, - const char* moduleName, - const char* className, - DOME_ForeignFn allocate, - DOME_FinalizerFn finalize) -``` -Register the `allocate` and `finalize` methods for `className` in `moduleName`, so that instances of the foreign class can be allocated, and optionally finalized. -The `finalize` method is your final chance to deal with the userdata attached to your foreign class. You won't have VM access inside this method. -DOME creates a copy of the `className`, so you are able to free the pointer if necessary. - -Returns `DOME_RESULT_SUCCESS` if the class is registered and `DOME_RESULT_FAILURE` otherwise. Failure will occur if `allocate` method is provided. The `finalize` argument can optionally be `NULL`. - - -#### method: registerFn -```c -DOME_Result registerFn(DOME_Context ctx, - const char* name, - const char* signature, - DOME_ForeignFn method) -``` -Register `method` as the function to call for the foreign method specified by `signature` in the module `name`. -DOME creates a copy of the `signature`, so you are able to free the pointer if necessary. -Returns `DOME_RESULT_SUCCESS` if the function was successfully registered, and `DOME_RESULT_FAILURE` otherwise. - -The format for the `signature` string is as follows: - * `static` if the method is a static class method, followed by a space, otherwise both are omitted. - * `ClassName` for the class method being declared, followed by a period (`.`) - * `methodName` which is the name of the field/method being exposed. - - If this is a field getter, nothing else is needed. - - If this is a field setter, add `=(_)` - - If this is a method, then parenthesis and a comma seperated list of underscores (`_`) follow, for the number of arguments the method takes. - - You can also use the setter and getter syntax for the class' subscript operator `[]`, which can be defined with one or more parameters. - - Wren methods can have up to 16 arguments, and are overloaded by arity. For example, `Test.do(_)` is considered different to `Test.do(_,_)` and so on. - -#### method: lockModule -```c -void lockModule(DOME_Context ctx, const char* name) -``` -This marks the module `name` as locked, so that further functions cannot modify it. It is recommended to do this after you have registered all the methods for your module, however there is no requirement to. - - - - -#### method: getContext -```c -DOME_Context getContext(WrenVM* vm) -``` -This allows foreign functions called by the Wren VM to access the current DOME context, to call various APIs. - -#### method: log -```c -void log(DOME_Context ctx, const char* text, ...) -``` - -Using this method allows for formatted output of `text` to the various debugging outputs DOME uses (stdout, a debug console on Windows and a DOME-log.txt file). - -You can use C-style specifiers for the `text` string, as used in the `printf` family of functions. - -## Wren - -You have access to a subset of the [Wren slot API](https://wren.io/embedding/slots-and-handles.html) in order to access parameters and return values in foreign methods. -The methods are incredibly well documented in the [Wren public header](https://github.com/wren-lang/wren/blob/main/src/include/wren.h), so we will not be documenting the functions here. - -You do not need to include the `wren.h` header in your application, as `dome.h` includes everything you need. - -### Acquisition - -```c -WREN_API_v0* wren = (WREN_API_v0*)DOME_getAPI(API_WREN, WREN_API_VERSION); -``` - -### Methods -This is a list of provided methods: -```c - void ensureSlots(WrenVM* vm, int slotCount); - void setSlotNull(WrenVM* vm, int slot); - void setSlotBool(WrenVM* vm, int slot, bool value); - void setSlotDouble(WrenVM* vm, int slot, double value); - void setSlotString(WrenVM* vm, int slot, const char* text); - void setSlotBytesWrenVM* vm, int slot, const char* data, size_t length); - void* setSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t length); - bool getSlotBool(WrenVM* vm, int slot); - double getSlotDouble(WrenVM* vm, int slot); -const char* getSlotString(WrenVM* vm, int slot); -const char* getSlotBytes(WrenVM* vm, int slot, int* length); - - WrenType getSlotType(WrenVM* vm, int slot); - - void setSlotNewList(WrenVM* vm, int slot); - int getListCount(WrenVM* vm, int slot); - void getListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - void setListElement(WrenVM* vm, int listSlot, int index, int elementSlot); - void insertInList(WrenVM* vm, int listSlot, int index, int elementSlot); - - void setSlotNewMap(WrenVM* vm, int slot); - int getMapCount(WrenVM* vm, int slot); - bool getMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); - void getMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - void setMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); - void removeMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); - - -WrenInterpretResult interpret(WrenVM* vm, const char* module, const char* source); -WrenInterpretResult call(WrenVM* vm, WrenHandle* method); - - bool hasModule(WrenVM* vm, const char* module); - bool hasVariable(WrenVM* vm, const char* module, const char* name); - void getVariable(WrenVM* vm, const char* module, const char* name, int slot); - WrenHandle* getSlotHandle(WrenVM* vm, int slot); - void setSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); - void releaseHandle(WrenVM* vm, WrenHandle* handle); - void abortFiber(WrenVM* vm, int slot); -``` - -### Module Embedding - -If your plugin registers a Wren module, you can embed the source of that module in your plugin by using DOME's built-in `--embed` command, which will convert it into a C include file. - -```sh -$ dome -e | --embed sourceFile [moduleVariableName] [destinationFile] -``` - -Example: - -```sh -$ dome -e external.wren source external.wren.inc -``` - -This command will use `external.wren` to generate `external.wren.inc`, which contains the variable `sourceModule` for including in C/C++ source code. - -## Audio - -This set of APIs gives you access to DOME's audio engine, to provide your own audio channel implementations. You can use this to synthesize sounds, or play custom audio formats. - -### Acquisition - -```c -AUDIO_API_v0* wren = (AUDIO_API_v0*)DOME_getAPI(API_AUDIO, AUDIO_API_VERSION); -``` - -### Enums - -#### enum: CHANNEL_STATE - -Audio channels are enabled and disabled based on a state, which is represented by this enum. Supported states are the following: - -```c -enum CHANNEL_STATE { - CHANNEL_INITIALIZE, - CHANNEL_TO_PLAY, - CHANNEL_PLAYING, - CHANNEL_STOPPING, - CHANNEL_STOPPED -} -``` - -### Function Signatures - -#### function: CHANNEL_mix -`CHANNEL_mix` functions have a signature of `void mix(CHANNEL_REF ref, float* buffer, size_t sampleRequestSize)`. +Below is a table explaining the available services and their purpose. - * `ref` is a reference to the channel being mixed. - * `buffer` is an interleaved stereo buffer to write your audio data into. One sample is two values, for left and right, so `buffer` is `2 * sampleRequestSize` in size. - -This callback is called on DOME's Audio Engine mixer thread. It is essential that you avoid any slow operations (memory allocation, network) or you risk interruptions to the audio playback. - -#### function: CHANNEL_callback -`CHANNEL_callback` functions have this signature: `void callback(CHANNEL_REF ref, WrenVM* vm)`. - - -### Methods - -#### method: channelCreate -```c -CHANNEL_REF channelCreate(DOME_Context ctx, - CHANNEL_mix mix, - CHANNEL_callback update, - CHANNEL_callback finish, - void* userdata); -``` - -When you create a new audio channel, you must supply callbacks for mixing, updating and finalizing the channel. This allows it to play nicely within DOME's expected audio lifecycle. - -This method creates a channel with the specified callbacks and returns its corresponding CHANNEL_REF value, which can be used to manipulate the channel's state during execution. The channel will be created in the state `CHANNEL_INITIALIZE`, which gives you the opportunity to set up the channel configuration before it is played. - -The callbacks work like this: - - `update` is called once a frame, and can be used for safely modifying the state of the channel data. This callback holds a lock over the mixer thread, so avoid holding it for too long. - - `finish` is called once the channel has been set to `STOPPED`, before its memory is released. It is safe to expect that the channel will not be played again. - -The `userdata` is a pointer set by the plugin developer, which can be used to pass through associated data, and retrieved by [`getData(ref)`](#method-getdata). You are responsible for the management of the memory pointed to by that pointer and should avoid modifying the contents of the memory outside of the provided callbacks. - - -#### method: getData -```c -void* getData(CHANNEL_REF ref) -``` -Fetch the `userdata` pointer for the given channel `ref`. - -#### method: getState -```c -CHANNEL_STATE getState(CHANNEL_REF ref) -``` -Get the current [state](#enum-channel_state) of the channel specified by `ref`. - -#### method: setState -```c -void setState(CHANNEL_REF ref, CHANNEL_STATE state) -``` -This allows you to specify the channel's [state](#enum-channel_state). DOME will only mix in channels in the following states: `CHANNEL_PLAYING` and `CHANNEL_STOPPING`. - -#### method: stop -```c -void stop(CHANNEL_REF ref) -``` -Marks the audio channel as having stopped. This means that DOME will no longer play this channel. It will call the `finish` callback at it's next opportunity. +| Service | Description | +|-------------------------|------------------------| +| [Core](core) | Engine utilities and module registration | +| [Wren](wren) | A subset of the Wren API for working with foreign classes. | +| [Audio](audio) | Access DOME's audio engine to provide your own audio. | +| [Bitmap](bitmap) | Load images and handle bitmap data. | +| [Canvas](canvas) | Draw to DOME's built-in canvas. | +| [I/O](io) | Access the host filesystem. | diff --git a/docs/plugins/io.md b/docs/plugins/io.md new file mode 100644 index 00000000..b2e1d8ff --- /dev/null +++ b/docs/plugins/io.md @@ -0,0 +1,28 @@ +[< Back](.) + +I/O +=============== + +This set of APIs allows you to access the host filesystem to read files. + + * [Acquisition](#acquistion) + * [method: readfile](#method-readfile) + +## Acquisition + +```c +IO_API_v0* io = (IO_API_v0*)DOME_getAPI(API_IO, IO_API_VERSION); +``` + +## Methods + +### method: readFile +```c +void* readFile(DOME_Context ctx, const char* path, size_t* length); +``` +Synchronously reads the file located at `path` to memory. The size of the file +in bytes is stored in the location pointed to by `length`. You are responsible +for freeing the returned pointer when you are done using it. + +If there is a problem loading the file, this method will return `NULL`. You can +find out why using `core->getLastError(DOME_Context ctx)`; diff --git a/docs/plugins/wren.md b/docs/plugins/wren.md new file mode 100644 index 00000000..809d38a2 --- /dev/null +++ b/docs/plugins/wren.md @@ -0,0 +1,78 @@ +[< Back](.) + +Wren +=============== + +You have access to a subset of the [Wren slot API](https://wren.io/embedding/slots-and-handles.html) in order to access parameters and return values in foreign methods. +The methods are incredibly well documented in the [Wren public header](https://github.com/wren-lang/wren/blob/main/src/include/wren.h), so we will not be documenting the functions here. + +You do not need to include the `wren.h` header in your application, as `dome.h` includes everything you need. + + * [Acquisition](#acquistion) + * [Module Embedding](#module-embedding) + +## Acquisition + +```c +WREN_API_v0* wren = (WREN_API_v0*)DOME_getAPI(API_WREN, WREN_API_VERSION); +``` + +## Methods +This is a list of provided methods: +```c + void ensureSlots(WrenVM* vm, int slotCount); + void setSlotNull(WrenVM* vm, int slot); + void setSlotBool(WrenVM* vm, int slot, bool value); + void setSlotDouble(WrenVM* vm, int slot, double value); + void setSlotString(WrenVM* vm, int slot, const char* text); + void setSlotBytesWrenVM* vm, int slot, const char* data, size_t length); + void* setSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t length); + bool getSlotBool(WrenVM* vm, int slot); + double getSlotDouble(WrenVM* vm, int slot); + const char* getSlotString(WrenVM* vm, int slot); + const char* getSlotBytes(WrenVM* vm, int slot, int* length); + void* getSlotForeign(WrenVM* vm, int slot); + + WrenType getSlotType(WrenVM* vm, int slot); + + void setSlotNewList(WrenVM* vm, int slot); + int getListCount(WrenVM* vm, int slot); + void getListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + void setListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + void insertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + + void setSlotNewMap(WrenVM* vm, int slot); + int getMapCount(WrenVM* vm, int slot); + bool getMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); + void getMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + void setMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + void removeMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot); + + +WrenInterpretResult interpret(WrenVM* vm, const char* module, const char* source); +WrenInterpretResult call(WrenVM* vm, WrenHandle* method); + + bool hasModule(WrenVM* vm, const char* module); + bool hasVariable(WrenVM* vm, const char* module, const char* name); + void getVariable(WrenVM* vm, const char* module, const char* name, int slot); + WrenHandle* getSlotHandle(WrenVM* vm, int slot); + void setSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); + void releaseHandle(WrenVM* vm, WrenHandle* handle); + void abortFiber(WrenVM* vm, int slot); +``` + +## Module Embedding + +If your plugin registers a Wren module, you can embed the source of that module in your plugin by using DOME's built-in `embed` subcommand, which will convert it into a C include file. + +```sh +$ dome embed sourceFile [moduleVariableName] [destinationFile] +``` + +Example: + +```sh +$ dome embed external.wren sourceModule external.wren.inc +``` + +This command will use `external.wren` to generate `external.wren.inc`, which contains the variable `sourceModule` for including in C/C++ source code. diff --git a/examples/circle/main.wren b/examples/circle/main.wren new file mode 100644 index 00000000..597bc44b --- /dev/null +++ b/examples/circle/main.wren @@ -0,0 +1,35 @@ +import "graphics" for Canvas, Color +import "input" for Keyboard + +var T = Color.rgb(255, 255, 255, 128) + +class Main { + construct new() {} + + init() { + _size = 0 + } + + update() { + if (Keyboard["space"].down) { + _size = _size + 1 + } + if (Keyboard["up"].down) { + _size = _size + 1 + } + if (Keyboard["down"].down) { + _size = _size - 1 + } + } + + draw(dt) { + Canvas.cls() + var cX = Canvas.width / 2 + var cY = Canvas.height / 2 + Canvas.circlefill(cX, cY, _size, Color.green) + Canvas.circle(cX, cY, _size, T) + Canvas.pset(cX, cY, Color.blue) + } +} + +var Game = Main.new() diff --git a/examples/ellipse/main.wren b/examples/ellipse/main.wren new file mode 100644 index 00000000..1fcddf4d --- /dev/null +++ b/examples/ellipse/main.wren @@ -0,0 +1,43 @@ +import "graphics" for Canvas, Color +import "input" for Keyboard + +var T = Color.rgb(255, 255, 255, 128) + +class Main { + construct new() {} + + init() { + _sizeX = 0 + _sizeY = 0 + } + + update() { + if (Keyboard["left"].down) { + _sizeX = _sizeX - 1 + } + if (Keyboard["right"].down) { + _sizeX = _sizeX + 1 + } + if (Keyboard["up"].down) { + _sizeY = _sizeY + 1 + } + if (Keyboard["down"].down) { + _sizeY = _sizeY - 1 + } + } + + draw(dt) { + Canvas.cls() + var cX = Canvas.width / 2 + var cY = Canvas.height / 2 + var x1 = cX - _sizeX + var x2 = cX + _sizeX + var y1 = cY - _sizeY + var y2 = cY + _sizeY + Canvas.ellipsefill(x1, y1, x2, y2, Color.green) + Canvas.ellipse(x1, y1, x2, y2, T) + Canvas.pset(cX, cY, Color.blue) + } +} + +var Game = Main.new() diff --git a/examples/raycaster/Makefile b/examples/raycaster/Makefile new file mode 100644 index 00000000..218ccc42 --- /dev/null +++ b/examples/raycaster/Makefile @@ -0,0 +1,23 @@ +NAME = raycaster +CFLAGS = -O0 -g -Wall +# CFLAGS += -fsanitize=address +.PHONY: all clean + +all: ${NAME}.dylib +SOURCES = $(wildcard *.c) $(wildcard *.h) +WREN_SOURCES = renderer.wren.inc + +renderer.wren.inc: renderer.wren + ../../dome embed renderer.wren rendererModuleSource renderer.wren.inc + +${NAME}.dylib: ${SOURCES} ${WREN_SOURCES} + ${CC} ${CFLAGS} -dynamiclib -o ${NAME}.dylib -I../../include plugin.c -undefined dynamic_lookup -lm + +${NAME}.so: ${SOURCES} ${WREN_SOURCES} + ${CC} ${CFLAGS} -shared -std=c11 -o ${NAME}.so -fPIC -I../../include plugin.c -lm + +${NAME}.dll: ${SOURCES} ${WREN_SOURCES} + ${CC} ${CFLAGS} -shared -std=gnu11 -shared -fPIC -I../../include plugin.c -Wl,--unresolved-symbols=ignore-in-object-files -o ${NAME}.dll -lm + +clean: + rm -f ${NAME}.dylib ${NAME}.so ${NAME}.dll *.wren.inc *.o diff --git a/examples/raycaster/domath.c b/examples/raycaster/domath.c new file mode 100644 index 00000000..e2bab2c1 --- /dev/null +++ b/examples/raycaster/domath.c @@ -0,0 +1,86 @@ +inline static double +getSign(double n) { + if (n < 0.0001) { + return -1; + } + if (n > 0.0001) { + return 1; + } + return 0; +} + +inline static double +clamp(double min, double x, double max) { + return fmax(min, fmin(x, max)); +} + + +typedef struct { + int32_t x; + int32_t y; +} V2i; + +typedef struct { + double x; + double y; +} V2; + +V2 V2_add(V2 a, V2 b) { + V2 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + return result; +} + +V2 V2_sub(V2 a, V2 b) { + V2 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + return result; +} + +V2 V2_hadamard(V2 a, V2 b) { + V2 result; + result.x = a.x * b.x; + result.y = a.y * b.y; + return result; +} + +V2 V2_mul(V2 a, double v) { + V2 result; + result.x = a.x * v; + result.y = a.y * v; + return result; +} + +double V2_length(V2 a) { + return sqrt(pow(a.x, 2) + pow(a.y, 2)); +} + +double V2_lengthSquared(V2 a) { + return pow(a.x, 2) + pow(a.y, 2); +} + +typedef struct { + double values[2 * 2]; +} M2; + +typedef struct { + uint32_t values[2 * 2]; +} M2i; + +typedef struct { + double values[3 * 3]; +} M3; + +typedef struct { + uint32_t values[3 * 3]; +} M3i; + +typedef struct { + double values[4 * 4]; +} M4; + +typedef struct { + uint32_t values[4 * 4]; +} M4i; diff --git a/examples/raycaster/gfx.c b/examples/raycaster/gfx.c new file mode 100644 index 00000000..6143ce02 --- /dev/null +++ b/examples/raycaster/gfx.c @@ -0,0 +1,8 @@ +void vLine(DOME_Context ctx, int32_t x, int32_t y0, uint32_t y1, DOME_Color color) { + uint32_t height = canvas->getHeight(ctx); + y0 = fmax(0, y0); + y1 = fmin(y1, height - 1); + for (int y = y0; y <= y1; y++) { + unsafePset(ctx, x, y, color); + } +} diff --git a/examples/raycaster/main.wren b/examples/raycaster/main.wren new file mode 100644 index 00000000..8c9a392d --- /dev/null +++ b/examples/raycaster/main.wren @@ -0,0 +1,117 @@ +import "math" for Vec +import "plugin" for Plugin +import "input" for Keyboard, Mouse +import "graphics" for Canvas, Color +import "dome" for Window + +Plugin.load("raycaster") +// The plugin will be initialised now + +// Plugins can register their own modules +import "raycaster" for Raycaster, WorldTile +var SPEED = 0.1 +var R_SPEED = 0.75 + +class Main { + construct new() {} + + init() { + var SCALE = 2 + var C_SCALE = 2 + Canvas.resize(C_SCALE * 320, C_SCALE* 200) + Window.resize(SCALE*Canvas.width, SCALE*Canvas.height) + Window.lockstep = true + + _pos = Vec.new(4, 4) + _angle = 0 + computeDirection() + + // and allocators for foreign classes + _raycaster = Raycaster.init() + _raycaster.setDimensions(14, 14) + _raycaster.setPosition(_pos.x, _pos.y) + _raycaster.setAngle(_angle) + _raycaster.loadTexture("res/wall1.png") + _raycaster.loadTexture("res/wall2.png") + _raycaster.loadTexture("res/wall3.png") + _raycaster.loadTexture("res/wall4.png") + _raycaster.loadTexture("res/blankwall.png") + _raycaster.loadTexture("res/blankwall.png") + _raycaster.loadTexture("res/floor.png") + _raycaster.loadTexture("res/ceil.png") + _raycaster.loadTexture("res/guard-test.png") + for (y in 0...14) { + for (x in 0...14) { + if (x == 0 || y == 0 || x == 13 || y == 13) { + _raycaster.tile(x, y).solid(true).wallTextureId(1) + } else { + _raycaster.tile(x, y).solid(false).floorTextureId(7).ceilingTextureId(8) + } + } + } + + + + _raycaster.tile(8, 7).wallTextureId(1).solid(true) + _raycaster.tile(8, 9).wallTextureId(1).solid(true) + _tile = _raycaster.tile(8, 8).wallTextureId(5).state(1).mode(1).door(true).ceilingTextureId(3) + + _raycaster.pushObject(12, 12, 9) + _raycaster.pushObject(12, 11, 9) + _raycaster.pushObject(10, 11, 9) + } + + computeDirection() { + var rads = _angle * Num.pi / 180 + _dir = Vec.new(rads.cos, rads.sin) + _perp = Vec.new(-_dir.y, _dir.x) + } + + update() { + // TODO: normalise movement + if (Keyboard["a"].down) { + _pos = _pos - _perp * SPEED + _raycaster.setPosition(_pos.x, _pos.y) + } + if (Keyboard["d"].down) { + _pos = _pos + _perp * SPEED + _raycaster.setPosition(_pos.x, _pos.y) + } + if (Keyboard["w"].down || Keyboard["up"].down) { + _pos = _pos + _dir*SPEED + _raycaster.setPosition(_pos.x, _pos.y) + } + if (Keyboard["s"].down || Keyboard["down"].down) { + _pos = _pos - _dir*SPEED + _raycaster.setPosition(_pos.x, _pos.y) + } + if (Keyboard["left"].down) { + _angle = _angle - R_SPEED + computeDirection() + _raycaster.setAngle(_angle) + } + if (Keyboard["right"].down) { + _angle = _angle + R_SPEED + computeDirection() + _raycaster.setAngle(_angle) + } + + _tile.state(_tile.state + _tile.mode * 0.05) + if (_tile.state <= 0 || _tile.state >= 1) { + _tile.mode(-1 * _tile.mode) + _tile.state(_tile.mode < 0 ? 1 : 0) + } + + _raycaster.update() + } + draw(alpha) { + Canvas.cls() + Canvas.rectfill(0, 0, Canvas.width, Canvas.height / 2, Color.lightgray) + Canvas.rectfill(0, Canvas.height / 2, Canvas.width, Canvas.height / 2, Color.darkgray) + _raycaster.draw(alpha) + Canvas.print("%(Mouse.x)", 0, 0, Color.white) + } +} + + +var Game = Main.new() diff --git a/examples/raycaster/map.dat b/examples/raycaster/map.dat new file mode 100644 index 00000000..21aacf5a --- /dev/null +++ b/examples/raycaster/map.dat @@ -0,0 +1,65 @@ +NAME Hallway +DESCRIPTION A nifty place to hang out + + +DEFAULT + SOLID=false + FLOOR=1 + CEILING=2 +END + +TILE 0 + +END + +TILE 1 + SOLID=true + WALL="wall1" +END + +TILE 2 + SOLID=true + DOOR=true + WALL="door" +END + +TILE 3 + SOLID=true + THIN=true + WALL="wall1" +END + +OBJECTS + ADD 12, 12, 9 +END + + +LEVEL +MAP 24 24 +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,1,0,1,0,0,0,1, +1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, +1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,1,1,2,1,1,0,0,0,0,1,0,1,0,1,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,0,0,0,5,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +END + + diff --git a/examples/raycaster/object.c b/examples/raycaster/object.c new file mode 100644 index 00000000..9c87c660 --- /dev/null +++ b/examples/raycaster/object.c @@ -0,0 +1,91 @@ + +void OBJ_allocate(WrenVM* vm) { + OBJ_REF* ref = wren->setSlotNewForeign(vm, 0, 0, sizeof(OBJ_REF)); + WrenHandle* handle = wren->getSlotHandle(vm, 1); + ref->handle = handle; + ref->id = wren->getSlotDouble(vm, 2); +} + +void OBJ_finalize(void* data) { + OBJ_REF* ref = data; + // This is probably a bad idea + wren->releaseHandle(vm, ref->handle); + ref->id = 0; + ref->handle = NULL; +} + +void OBJ_remove(WrenVM* vm) { + OBJ_REF* ref = wren->getSlotForeign(vm, 0); + wren->ensureSlots(vm, 2); + wren->setSlotHandle(vm, 1, ref->handle); + RENDERER* renderer = wren->getSlotForeign(vm, 1); + + uint64_t id = ref->id; + size_t count = renderer->objectCount; + + for (size_t i = 0; i < count; i++) { + OBJ* item = &(renderer->objects[i]); + if (item->id == id) { + uint64_t last = count - 1; + renderer->objects[i] = renderer->objects[last]; + renderer->objects[last].id = 0; + renderer->objectCount--; + break; + } + } + // Handle if ID not found? +} + +#define OBJ_GETTER(fieldName, method, fieldType) \ + void OBJ_get##method(WrenVM* vm) { \ + OBJ_REF* ref = wren->getSlotForeign(vm, 0); \ + wren->ensureSlots(vm, 3); \ + wren->setSlotHandle(vm, 2, ref->handle); \ + RENDERER* renderer = wren->getSlotForeign(vm, 2); \ + OBJ* obj = RENDERER_getObject(renderer, ref->id); \ + wren->setSlot##fieldType(vm, 0, obj->fieldName); \ +} + +#define OBJ_SETTER(fieldName, method, fieldType) \ +void OBJ_set##method(WrenVM* vm) { \ + OBJ_REF* ref = wren->getSlotForeign(vm, 0); \ + wren->ensureSlots(vm, 3); \ + wren->setSlotHandle(vm, 2, ref->handle); \ + RENDERER* renderer = wren->getSlotForeign(vm, 2); \ + OBJ* obj = RENDERER_getObject(renderer, ref->id); \ + obj->fieldName = wren->getSlot##fieldType(vm, 1); \ +} + +OBJ_GETTER(id, Id, Double) +OBJ_GETTER(texture.id, TextureId, Double) +OBJ_SETTER(texture.id, TextureId, Double) +OBJ_GETTER(position.x, X, Double) +OBJ_SETTER(position.x, X, Double) +OBJ_GETTER(position.y, Y, Double) +OBJ_SETTER(position.y, Y, Double) +OBJ_GETTER(div.x, UDiv, Double) +OBJ_SETTER(div.x, UDiv, Double) +OBJ_GETTER(div.y, VDiv, Double) +OBJ_SETTER(div.y, VDiv, Double) +OBJ_GETTER(vMove, VMove, Double) +OBJ_SETTER(vMove, VMove, Double) + +void OBJ_register(DOME_Context ctx) { + core->registerClass(ctx, "raycaster", "WorldObject", OBJ_allocate, OBJ_finalize); + core->registerFn(ctx, "raycaster", "WorldObject.remove()", OBJ_remove); + core->registerFn(ctx, "raycaster", "WorldObject.id", OBJ_getId); + core->registerFn(ctx, "raycaster", "WorldObject.textureId", OBJ_getTextureId); + core->registerFn(ctx, "raycaster", "WorldObject.textureId=(_)", OBJ_setTextureId); + core->registerFn(ctx, "raycaster", "WorldObject.x", OBJ_getX); + core->registerFn(ctx, "raycaster", "WorldObject.x=(_)", OBJ_setX); + core->registerFn(ctx, "raycaster", "WorldObject.y", OBJ_getY); + core->registerFn(ctx, "raycaster", "WorldObject.y=(_)", OBJ_setY); + core->registerFn(ctx, "raycaster", "WorldObject.uDiv", OBJ_getUDiv); + core->registerFn(ctx, "raycaster", "WorldObject.uDiv=(_)", OBJ_setUDiv); + core->registerFn(ctx, "raycaster", "WorldObject.vDiv", OBJ_getVDiv); + core->registerFn(ctx, "raycaster", "WorldObject.vDiv=(_)", OBJ_setVDiv); + core->registerFn(ctx, "raycaster", "WorldObject.vMove", OBJ_getVMove); + core->registerFn(ctx, "raycaster", "WorldObject.vMove=(_)", OBJ_setVMove); + + +} diff --git a/examples/raycaster/plugin.c b/examples/raycaster/plugin.c new file mode 100644 index 00000000..9fadcebd --- /dev/null +++ b/examples/raycaster/plugin.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include "sbuf.c" + +#include "domath.c" +#include "dome.h" +#include "renderer.h" +#include "gfx.c" +#include "renderer.c" +#include "object.c" +#include "tile.c" +#include "texture.c" + +DOME_EXPORT DOME_Result PLUGIN_onInit(DOME_getAPIFunction DOME_getAPI, + DOME_Context ctx) { + + // Fetch the latest Core API and save it for later use. + core = DOME_getAPI(API_DOME, DOME_API_VERSION); + io = DOME_getAPI(API_IO, IO_API_VERSION); + canvas = DOME_getAPI(API_CANVAS, CANVAS_API_VERSION); + bitmap = DOME_getAPI(API_BITMAP, BITMAP_API_VERSION); + unsafePset = canvas->unsafePset; + wren = DOME_getAPI(API_WREN, WREN_API_VERSION); + vm = core->getVM(ctx); + core->log(ctx, "Initialising raycaster module\n"); + + // Register a module with it's associated source. + // Avoid giving the module a common name. + core->registerModule(ctx, "raycaster", rendererModuleSource); + + RENDERER_register(ctx); + TILE_register(ctx); + OBJ_register(ctx); + TEXTURE_REF_register(ctx); + + core->lockModule(ctx, "raycaster"); + + /* + char* result = io->readFile(ctx, "aiafdfafdadf", NULL); + if (result == NULL) { + printf("%s\n", core->getLastError(ctx)); + } + */ + // Returning anything other than SUCCESS here will result in the current fiber + // aborting. Use this to indicate if your plugin initialised successfully. + return DOME_RESULT_SUCCESS; +} + +DOME_EXPORT DOME_Result PLUGIN_preUpdate(DOME_Context ctx) { + // core->log(ctx, "a: 0x%02hX, r: 0x%02hX, g: 0x%02hX, b: 0x%02hX\n", color.component.a, color.component.r, color.component.g, color.component.b); + return DOME_RESULT_SUCCESS; +} + +DOME_EXPORT DOME_Result PLUGIN_postUpdate(DOME_Context ctx) { + return DOME_RESULT_SUCCESS; +} +DOME_EXPORT DOME_Result PLUGIN_preDraw(DOME_Context ctx) { + return DOME_RESULT_SUCCESS; +} +DOME_EXPORT DOME_Result PLUGIN_postDraw(DOME_Context ctx) { + return DOME_RESULT_SUCCESS; +} + +DOME_EXPORT DOME_Result PLUGIN_onShutdown(DOME_Context ctx) { + return DOME_RESULT_SUCCESS; +} + diff --git a/examples/raycaster/raycaster.dylib b/examples/raycaster/raycaster.dylib new file mode 100755 index 00000000..5ee16134 Binary files /dev/null and b/examples/raycaster/raycaster.dylib differ diff --git a/examples/raycaster/renderer.c b/examples/raycaster/renderer.c new file mode 100644 index 00000000..c0094148 --- /dev/null +++ b/examples/raycaster/renderer.c @@ -0,0 +1,588 @@ +#include "renderer.wren.inc" +#define worldTile(renderer, x, y) renderer->map.tiles[(int)y * renderer->map.width + (int)x] +#define getTileFrom(ref, renderer) worldTile(renderer, ref->x, ref->y) + +void RENDERER_allocate(WrenVM* vm) { + DOME_Context ctx = core->getContext(vm); + RENDERER* renderer = wren->setSlotNewForeign(vm, 0, 0, sizeof(RENDERER)); + memset(renderer, 0, sizeof(RENDERER)); + + renderer->position = (V2) { 1, 1 }; + renderer->direction = (V2) { 0, 1 }; + renderer->cameraPlane = (V2) { -1, 0 }; + + renderer->width = canvas->getWidth(ctx); + renderer->height = canvas->getHeight(ctx); + renderer->lookup = calloc(renderer->height, sizeof(double)); + for (int y = 0; y < renderer->height; y++) { + renderer->lookup[y] = ((double)renderer->height / (2.0 * y - renderer->height)); + } + renderer->z = calloc(renderer->width, sizeof(double)); + for (int x = 0; x < renderer->width; x++) { + renderer->z[x] = 0; + } + renderer->textureList = NULL; + renderer->objects = NULL; + renderer->map.tiles = NULL; + renderer->map.width = 0; + renderer->map.height = 0; + + // An ID of zero means the entry is not in use. + // Valid IDs are > 0 + renderer->nextId = 1; +} + + +void RENDERER_pushObject(WrenVM* vm) { + RENDERER* renderer = wren->getSlotForeign(vm, 0); + double x = wren->getSlotDouble(vm, 1); + double y = wren->getSlotDouble(vm, 2); + uint32_t textureId = wren->getSlotDouble(vm, 3); + + OBJ sprite; + // Safety init + memset(&sprite, 0, sizeof(OBJ)); + sprite.id = renderer->nextId++; + sprite.div.x = 1; + sprite.div.y = 1; + sprite.vMove = 0; + sprite.texture.id = textureId; + sprite.position = (V2) {x, y}; + + sb_push(renderer->objects, sprite); + renderer->objectCount++; + + wren->setSlotDouble(vm, 0, sprite.id); +} + + +void RENDERER_finalize(void* data) { + RENDERER* renderer = data; + free(renderer->lookup); + free(renderer->z); + free(renderer->map.tiles); + + for (int i = 0; i < sb_count(renderer->textureList); i++) { + bitmap->free(renderer->textureList[i]); + } + sb_free(renderer->textureList); + sb_free(renderer->objects); +} + +void RENDERER_loadTexture(WrenVM* vm) { + DOME_Context ctx = core->getContext(vm); + RENDERER* renderer = wren->getSlotForeign(vm, 0); + const char* path = wren->getSlotString(vm, 1); + DOME_Bitmap* texture = bitmap->fromFile(ctx, path); + sb_push(renderer->textureList, texture); + if (renderer->textureList == NULL) { + abort(); + } + size_t newId = sb_count(renderer->textureList); + wren->setSlotDouble(vm, 0, newId); + printf("Assigning texture slot %zu\n", newId); +} + +void RENDERER_setPosition(WrenVM* vm) { + double x = wren->getSlotDouble(vm, 1); + double y = wren->getSlotDouble(vm, 2); + RENDERER* renderer = wren->getSlotForeign(vm, 0); + renderer->position.x = x; + renderer->position.y = y; +} + +void RENDERER_setAngle(WrenVM* vm) { + double angle = wren->getSlotDouble(vm, 1); + double rads = angle * M_PI / 180.0; + RENDERER* renderer = wren->getSlotForeign(vm, 0); + renderer->direction.x = cos(rads); + renderer->direction.y = sin(rads); + renderer->cameraPlane.x = -renderer->direction.y; + renderer->cameraPlane.y = renderer->direction.x; +} + +void RENDERER_setDimensions(WrenVM* vm) { + uint64_t width = wren->getSlotDouble(vm, 1); + uint64_t height = wren->getSlotDouble(vm, 2); + RENDERER* renderer = wren->getSlotForeign(vm, 0); + renderer->map.width = width; + renderer->map.height = height; + renderer->map.tiles = realloc(renderer->map.tiles, width * height * sizeof(TILE)); + memset(renderer->map.tiles, 0, width * height * sizeof(TILE)); +} + +OBJ* RENDERER_getObject(RENDERER* renderer, uint64_t id) { + OBJ* objects = renderer->objects; + size_t count = renderer->objectCount; + + for (size_t i = 0; i < count; i++) { + OBJ* item = objects + i; + if (item->id == id) { + return item; + } + } + return NULL; +} + +int RENDERER_compareZBuffer (void* ref, const void * a, const void * b) +{ + RENDERER* renderer = ref; + OBJ* aV = (OBJ*)a; + OBJ* bV = (OBJ*)b; + return V2_lengthSquared(V2_sub(renderer->position, bV->position)) - V2_lengthSquared(V2_sub(renderer->position, aV->position)); +} + +void RENDERER_sort(RENDERER* renderer) { + // Assume that the list of objects is /nearly/ sorted, frame over frame + // So insertion sort gives the best performance on average. + + OBJ* objects = renderer->objects; + size_t count = renderer->objectCount; + + for (size_t i = 1; i < count; i++) { + OBJ item = objects[i]; + assert(item.id > 0); + size_t previous = i; + while (previous > 0 && RENDERER_compareZBuffer(renderer, &item, &objects[previous - 1]) <= 0) { + objects[previous] = objects[previous - 1]; + previous -= 1; + } + objects[previous] = item; + } +} + +CAST_RESULT castRay(RENDERER* renderer, V2 rayPosition, V2 rayDirection, bool ignoreDoors) { + V2 sideDistance = {0, 0}; + CAST_RESULT result; + sideDistance.x = sqrt(1.0 + pow(rayDirection.y, 2) / pow(rayDirection.x, 2)); + sideDistance.y = sqrt(1.0 + pow(rayDirection.x, 2) / pow(rayDirection.y, 2)); + V2 nextSideDistance; + V2i mapPos = { rayPosition.x, rayPosition.y }; + V2i stepDirection = {0, 0}; + if (rayDirection.x < 0) { + stepDirection.x = -1; + nextSideDistance.x = (rayPosition.x - mapPos.x) * sideDistance.x; + } else { + stepDirection.x = 1; + nextSideDistance.x = (mapPos.x + 1.0 - rayPosition.x) * sideDistance.x; + } + + if (rayDirection.y < 0) { + stepDirection.y = -1; + nextSideDistance.y = (rayPosition.y - mapPos.y) * sideDistance.y; + } else { + stepDirection.y = 1; + nextSideDistance.y = (mapPos.y + 1.0 - rayPosition.y) * sideDistance.y; + } + bool hit = false; + int side = 0; + size_t mapPitch = renderer->map.width; + TILE* tiles = renderer->map.tiles; + while(!hit) { + if (nextSideDistance.x < nextSideDistance.y) { + nextSideDistance.x += sideDistance.x; + mapPos.x += stepDirection.x; + side = 0; + } else { + nextSideDistance.y += sideDistance.y; + mapPos.y += stepDirection.y; + side = 1; + } + TILE tile; + + if (mapPos.x < 0 || mapPos.x >= renderer->map.width || mapPos.y < 0 || mapPos.y >= renderer->map.height) { + hit = true; + result.inBounds = false; + } else { + if (renderer->map.width * renderer->map.height == 0) { + tile = VOID_TILE; + } else { + tile = worldTile(renderer, mapPos.x, mapPos.y); + } + result.inBounds = true; + // Check for door and thin walls here + if (tile.thin || tile.door) { + double doorState = 1.0; + if (tile.door) { + doorState = ignoreDoors ? 1 : tile.state; + } + double adj; + double ray_mult; + if (side == 0) { + adj = mapPos.x - rayPosition.x + 1.0; + if (rayPosition.x < mapPos.x) { + adj = adj - 1; + } + ray_mult = adj / rayDirection.x; + } else { + adj = mapPos.y - rayPosition.y + 1.0; + if (rayPosition.y < mapPos.y) { + adj = adj - 1; + } + ray_mult = adj / rayDirection.y; + } + double rye2 = rayPosition.y + rayDirection.y * ray_mult; + double rxe2 = rayPosition.x + rayDirection.x * ray_mult; + double trueDeltaX = sideDistance.x; + double trueDeltaY = sideDistance.y; + if (fabs(rayDirection.y) < 0.01) { + trueDeltaY = 100; + } + if (fabs(rayDirection.x) < 0.01) { + trueDeltaX = 100; + } + double offsetX = 0; + double offsetY = 0; + if (tile.door) { + offsetX = 0.5; + offsetY = 0.5; + } + if (tile.thin) { + offsetX = 0.5 + clamp(-0.5, tile.offset * getSign(stepDirection.x), 0.5); + offsetY = 0.5 + clamp(-0.5, tile.offset * getSign(stepDirection.y), 0.5); + } + if (side == 0) { + double true_y_step = sqrt(trueDeltaX * trueDeltaX - 1); + double half_step_in_y = rye2 + (stepDirection.y * true_y_step) * offsetX; + hit = ((int)half_step_in_y == mapPos.x) && fabs(1 - 2 * (half_step_in_y - mapPos.y)) > 1 - doorState; + } else { + double true_x_step = sqrt(trueDeltaY * trueDeltaY - 1); + double half_step_in_x = rxe2 + (stepDirection.x * true_x_step) * offsetY; + hit = ((int)half_step_in_x == mapPos.x) && fabs(1 - 2 * (half_step_in_x - mapPos.x)) > 1 - doorState; + } + } else { + hit = tile.solid; + } + } + } + + result.mapPos = (V2){ (int)floor(mapPos.x), (int)floor(mapPos.y) }; + result.stepDirection = stepDirection; + result.side = side; + + return result; +} + + +void RENDERER_update(WrenVM* vm) { + DOME_Context ctx = core->getContext(vm); + RENDERER* renderer = wren->getSlotForeign(vm, 0); + + // sort objects by z + RENDERER_sort(renderer); +} + +void RENDERER_draw(WrenVM* vm) { + DOME_Context ctx = core->getContext(vm); + RENDERER* renderer = wren->getSlotForeign(vm, 0); + MAP map = renderer->map; + double alpha = wren->getSlotDouble(vm, 1); + // Retrieve the DOME Context from the VM. This is needed for many things. + DOME_Color color; + + V2 position = renderer->position; + V2 rayPosition = renderer->position; + V2 direction = renderer->direction; + V2 camera = renderer->cameraPlane; + + int w = renderer->width; + int h = renderer->height; + + int texWidth = 64; + int texHeight = 64; + + // Vertical position of the camera. + double posZ = 0.5 * h; + V2 rayDirection0 = V2_add(direction, V2_mul(camera, -1)); + V2 rayDirection1 = V2_add(direction, V2_mul(camera, 1)); + double rayDirX0 = rayDirection0.x; + double rayDirY0 = rayDirection0.y; + double rayDirX1 = rayDirection1.x; + double rayDirY1 = rayDirection1.y; + // floor casting + for (int y = h/2 + 1; y < h; y++) { + + // Current y position compared to the center of the screen (the horizon) + int p = y - (h/2); // y - (h / 2); + // Horizontal distance from the camera to the floor for the current row. + // 0.5 is the z position exactly in the middle between floor and ceiling. + double rowDistance = posZ / (double)p; + // calculate the real world step vector we have to add for each x (parallel to camera plane) + // adding step by step avoids multiplications with a weight in the inner loop + double floorStepX = rowDistance * (rayDirX1 - rayDirX0) / ((double)w); + double floorStepY = rowDistance * (rayDirY1 - rayDirY0) / ((double)w); + + // real world coordinates of the leftmost column. This will be updated as we step to the right. + double floorX = position.x + rowDistance * rayDirX0; + double floorY = position.y + rowDistance * rayDirY0; + + for(int x = 0; x < w; x++) { + // the cell coord is simply got from the integer parts of floorX and floorY + int cellX = (int)(floorX); + int cellY = (int)(floorY); + + // get the texture coordinate from the fractional part + + floorX += floorStepX; + floorY += floorStepY; + if (cellX < 0 || cellY < 0 || cellX >= renderer->map.width || cellY >= renderer->map.height) { + continue; + } + + DOME_Color color; + + // floor + DOME_Bitmap* texture = NULL; + TILE tile = worldTile(renderer, cellX, cellY); + uint32_t textureId = tile.floor.id; + if (textureId > 0) { + texture = renderer->textureList[textureId - 1]; + texWidth = texture->width; + texHeight = texture->height; + int texX = (uint32_t)(texWidth * (floorX - cellX)) % texWidth; + int texY = (uint32_t)(texHeight * (floorY - cellY)) % texHeight; + color = bitmap->pget(texture, texX, texY); + unsafePset(ctx, x, y, color); + } + textureId = tile.ceiling.id; + if (textureId > 0) { + texture = renderer->textureList[textureId - 1]; + texWidth = texture->width; + texHeight = texture->height; + int texX = (uint32_t)(texWidth * (floorX - cellX)) % texWidth; + int texY = (uint32_t)(texHeight * (floorY - cellY)) % texHeight; + color = bitmap->pget(texture, texX, texY); + unsafePset(ctx, x, h - y - 1, color); + } + + //ceiling (symmetrical, at screenHeight - y - 1 instead of y) + // color = texture[ceilingTexture][texWidth * ty + tx]; + // color = (color >> 1) & 8355711; // make a bit darker + // buffer[screenHeight - y - 1][x] = color; + } + } + + // Wall casting + for(int x = 0; x < w; x++) { + // Perform DDA first + double cameraX = 2 * (x / (double)w) - 1; + V2 rayDirection = V2_add(direction, V2_mul(camera, cameraX)); + CAST_RESULT cast = castRay(renderer, rayPosition, rayDirection, false); + V2 mapPos = { cast.mapPos.x, cast.mapPos.y }; + int side = cast.side; + V2i stepDirection = cast.stepDirection; + TILE tile; + int textureId = 0; + + TEXTURE_REF ref = {}; + DOME_Bitmap* texture = NULL; + if (renderer->map.width * renderer->map.height == 0) { + tile = VOID_TILE; + } else if (cast.inBounds) { + tile = worldTile(renderer, mapPos.x, mapPos.y); + ref = tile.wall; + textureId = ref.id; + if (textureId > 0 && textureId <= sb_count(renderer->textureList)) { + texture = renderer->textureList[textureId - 1]; + texWidth = texture->width; + texHeight = texture->height; + } else { + tile = (TILE){}; + memset(&tile, 0, sizeof(TILE)); + textureId = 0; + } + } else { + tile = (TILE){}; + memset(&tile, 0, sizeof(TILE)); + textureId = 0; + } + if (x == 0) { + // printf("%i - %f, %f\n", textureId, rayDirection.x, rayDirection.y); + } + + double offsetX = 0; + double offsetY = 0; + if (tile.door) { + offsetX = 0.5; + offsetY = 0.5; + } + if (tile.thin) { + offsetX = 0.5 + clamp(-0.5, tile.offset * getSign(stepDirection.x), 0.5); + offsetY = 0.5 + clamp(-0.5, tile.offset * getSign(stepDirection.y), 0.5); + } + if (tile.door || offsetX != 0 || offsetY != 0) { + if (side == 0) { + mapPos.x = mapPos.x + stepDirection.x * offsetX; + } else { + mapPos.y = mapPos.y + stepDirection.y * offsetY; + } + + } + double perpWallDistance; + if (side == 0) { + perpWallDistance = fabs((mapPos.x - rayPosition.x + (1 - stepDirection.x) / 2.0) / rayDirection.x); + } else { + perpWallDistance = fabs((mapPos.y - rayPosition.y + (1 - stepDirection.y) / 2.0) / rayDirection.y); + } + renderer->z[x] = perpWallDistance; + + // Calculate perspective of wall-slice + double halfH = (double)h / 2.0; + double lineHeight = fmax(0, ((double)h / perpWallDistance)); + double drawStart = (-lineHeight / 2.0) + halfH; + double drawEnd = (lineHeight / 2.0) + halfH; + drawStart = clamp(0, drawStart, h - 1); + drawEnd = clamp(0, drawEnd, h - 1); + + double wallX; + if (side == 0) { + wallX = rayPosition.y + perpWallDistance * rayDirection.y; + } else { + wallX = rayPosition.x + perpWallDistance * rayDirection.x; + } + wallX = wallX - floor(wallX); + int drawWallStart = fmax(0, (int)drawStart); + int drawWallEnd = fmin((int)ceil(drawEnd), h - 1); + DOME_Color color; + if (texture != NULL) { + // wallX = 0->1 of the renderable texture + // ref->min 0->1 for the texture data + // ref->max 0->1 for the texture data + // wallX + // min.x -> max.x + // start = min.x * texWidth + // end = max.x * texWidth + // start + (end - start) * wallX + // min.x * texWidth + (max.x * texWidth - min.x * texWidth) * wallX + // texWidth * (min.x + (max.x - min.x) * wallX) + V2 min = ref.min; + V2 max = ref.max; + int texX = floor((min.x + (max.x - min.x) * wallX) * (texWidth)); + int width = (max.x - min.x) * texWidth; + if (side == 0 && rayDirection.x < 0) { + texX = (width - 1) - texX; + } + if (side == 1 && rayDirection.y > 0) { + texX = (width - 1) - texX; + } + + texX = clamp(0, texX, width - 1); + assert(texX >= 0); + assert(texX < texWidth); + + int height = floor((max.y - min.y) * ((double)texHeight - 1.0)); + double texStep = (double)(height) / lineHeight; + double texPos = ((texHeight - 1.0) * min.y + ((drawStart) - halfH + (lineHeight / 2.0))) * texStep; + for (int y = drawWallStart; y < drawWallEnd; y++) { + int texY = clamp(min.y * texHeight, texPos, height); + assert(texY >= 0); + assert(texY < texHeight); + + color = bitmap->pget(texture, texX, texY); + if (side == 1) { + uint8_t alpha = color.component.a; + color.value = (color.value >> 1) & 8355711; + color.component.a = alpha; + } + assert(y < h); + assert(y >= 0); + + unsafePset(ctx, x, y, color); + texPos += texStep; + } + } else { + // No texture, just magenta + color.value = 0xFFFF00FF; + if (x == 0) { + // printf("Just magenta\n"); + } + + //give x and y sides different brightness + if (side == 1) { + color.component.r /= 2; + color.component.g /= 2; + color.component.b /= 2; + } + vLine(ctx, x, drawWallStart, drawWallEnd, color); + } + } + + size_t objCount = renderer->objectCount; + for (size_t i = 0; i < objCount; i++) { + OBJ obj = renderer->objects[i]; + double uDiv = obj.div.x; + double vDiv = obj.div.y; + double vMove = obj.vMove; + + // getSpriteTransform + V2 position = renderer->position; + V2 dir = direction; + V2 cam = camera; + double invDet = 1.0 / (-camera.x * dir.y + dir.x * camera.y); + V2 sprite = V2_sub(obj.position, position); + + V2 transform; + transform.x = invDet * (dir.x * sprite.y - dir.y * sprite.x); + transform.y = invDet * (cam.y * sprite.x - cam.x * sprite.y); + // end getSpriteTransform + + if (transform.y > 0) { + int vMoveScreen = floor(vMove / transform.y); + int spriteScreenX = floor((w / 2.0) * (1.0 + transform.x / transform.y)); + //prevent fish eye + int spriteHeight = floor(fabs(h / transform.y) / vDiv); + int drawStartY = floor(((h - spriteHeight) / 2.0) + vMoveScreen); + if (drawStartY < 0) { + drawStartY = 0; + } + int drawEndY = floor(((h + spriteHeight) / 2.0) + vMoveScreen); + if (drawEndY >= h) { + drawEndY = h - 1; + } + int spriteWidth = floor((fabs(h / transform.y) / uDiv) / 2.0); + int drawStartX = floor(spriteScreenX - spriteWidth); + if (drawStartX < 0) { + drawStartX = 0; + } + int drawEndX = floor(spriteScreenX + spriteWidth); + if (drawEndX >= w) { + drawEndX = w - 1; + } + + DOME_Bitmap* texture = renderer->textureList[obj.texture.id - 1]; + texWidth = texture->width; + texHeight = texture->height; + + for (int stripe = drawStartX; stripe < drawEndX; stripe++) { + int texX = abs((stripe - (-spriteWidth + spriteScreenX)) * texWidth / (spriteWidth * 2)); + + // Conditions for this if: + //1) it's in front of camera plane so you don't see things behind you + //2) it's on the screen (left) + //3) it's on the screen (right) + //4) ZBuffer, with perpendicular distance + + if (stripe > 0 && stripe < w && transform.y < renderer->z[stripe]) { + for (int y = drawStartY; y < drawEndY; y++) { + int texY = fabs(((y - vMoveScreen) - (-spriteHeight / 2.0 + h / 2.0)) * texHeight / spriteHeight); + color = bitmap->pget(texture, texX, texY); + unsafePset(ctx, stripe, y, color); + } + } + } + } + } +} + +void RENDERER_register(DOME_Context ctx) { + core->registerClass(ctx, "raycaster", "Raycaster", RENDERER_allocate, RENDERER_finalize); + core->registerFn(ctx, "raycaster", "Raycaster.draw(_)", RENDERER_draw); + core->registerFn(ctx, "raycaster", "Raycaster.update()", RENDERER_update); + core->registerFn(ctx, "raycaster", "Raycaster.setAngle(_)", RENDERER_setAngle); + core->registerFn(ctx, "raycaster", "Raycaster.loadTexture(_)", RENDERER_loadTexture); + core->registerFn(ctx, "raycaster", "Raycaster.setPosition(_,_)", RENDERER_setPosition); + core->registerFn(ctx, "raycaster", "Raycaster.setDimensions(_,_)", RENDERER_setDimensions); + core->registerFn(ctx, "raycaster", "Raycaster.f_pushObject(_,_,_)", RENDERER_pushObject); +} + + diff --git a/examples/raycaster/renderer.h b/examples/raycaster/renderer.h new file mode 100644 index 00000000..1f1317ee --- /dev/null +++ b/examples/raycaster/renderer.h @@ -0,0 +1,90 @@ +typedef struct { + // If this is zero, default to color + uint64_t id; + + // TODO: Min/Max, or src/size? + // Other properties for animation + // x/y, cellSize etc + V2 min; + V2 max; +} TEXTURE_REF; + +typedef struct { + bool solid; + // door state + bool door; + bool locked; + int behaviour; // how does this door function? + double state; // how open are we, clamped [0,1] + int8_t mode; // opening/closing + + bool thin; + double offset; + + TEXTURE_REF wall; + TEXTURE_REF floor; + TEXTURE_REF ceiling; +} TILE; + + +typedef struct { + size_t width; + size_t height; + TILE* tiles; +} MAP; + +typedef struct { + uint64_t id; + V2 position; + V2 direction; + TEXTURE_REF texture; + V2 div; + double vMove; +} OBJ; + +typedef struct { + uint64_t id; + WrenHandle* handle; +} OBJ_REF; + +typedef struct { + V2 position; + V2 direction; + V2 cameraPlane; + + uint32_t width; + uint32_t height; + + double* lookup; + double* z; + + DOME_Bitmap** textureList; + + size_t objectCount; + OBJ* objects; + + MAP map; + uint64_t nextId; +} RENDERER; + +typedef struct { + size_t x; + size_t y; + WrenHandle* handle; +} TILE_REF; + +typedef struct { + V2 mapPos; + int side; + V2i stepDirection; + bool inBounds; +} CAST_RESULT; + +static CANVAS_API_v0* canvas; +static BITMAP_API_v0* bitmap; +static DOME_API_v0* core; +static IO_API_v0* io; +static WREN_API_v0* wren; +static void (*unsafePset)(DOME_Context, int32_t, int32_t, DOME_Color) = NULL; +static WrenVM* vm; +static TILE VOID_TILE = {}; diff --git a/examples/raycaster/renderer.wren b/examples/raycaster/renderer.wren new file mode 100644 index 00000000..3526d54e --- /dev/null +++ b/examples/raycaster/renderer.wren @@ -0,0 +1,131 @@ +foreign class Raycaster { + construct init() {} + foreign setPosition(x, y) + foreign setAngle(angle) + foreign setDimensions(width, height) + + foreign draw(alpha) + foreign update() + foreign loadTexture(path) + + foreign f_pushObject(x, y, textureId) + tile(x, y) { + return WorldTile.init(this, x, y) + } + + object(id) { + // Should we create an object if the id is not used? + return WorldObject.init(this, id) + } + + pushObject(x, y, textureId) { + var id = f_pushObject(x, y, textureId) + return WorldObject.init(this, id) + } +} + +foreign class WorldObject { + construct init(renderer, id) {} + + foreign remove() + + // TODO: Fluent API + foreign id + + foreign textureId + foreign textureId=(v) + foreign x + foreign x=(v) + foreign y + foreign y=(v) + foreign uDiv + foreign uDiv=(v) + foreign vDiv + foreign vDiv=(v) + foreign vMove + foreign vMove=(v) +} + +foreign class WorldTile { + construct init(renderer, x, y) {} + + thin(value) { + thin = value + return this + } + + offset(value) { + offset = value + return this + } + + wallTexture(value) { + wallTexture = value + return this + } + wallTextureId(value) { + wallTexture = TextureRef.init(value) + return this + } + + ceilingTextureId(value) { + ceilingTextureId = value + return this + } + + floorTextureId(value) { + floorTextureId = value + return this + } + + solid(value) { + solid = value + return this + } + + state(value) { + state = value + return this + } + + mode(value) { + mode = value + return this + } + + door(value) { + door = value + return this + } + + foreign wallTexture=(v) + // TODO: Get wallTexture + foreign ceilingTextureId + foreign ceilingTextureId=(v) + foreign floorTextureId + foreign floorTextureId=(v) + foreign solid + foreign solid=(v) + foreign door + foreign door=(v) + foreign state + foreign state=(v) + foreign mode + foreign mode=(v) + foreign offset + foreign offset=(v) + foreign thin + foreign thin=(v) +} + +foreign class TextureRef { + construct init(id) {} + construct init(id, min, max) { + this.min = min + this.max = max + } + + foreign id=(v) + foreign min=(list) + foreign max=(list) +} diff --git a/examples/raycaster/renderer.wren.inc b/examples/raycaster/renderer.wren.inc new file mode 100644 index 00000000..4ae39503 --- /dev/null +++ b/examples/raycaster/renderer.wren.inc @@ -0,0 +1,133 @@ +// auto-generated file, do not modify +const char rendererModuleSource[2141] = {'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'l', 'a', 's', 's', ' ', 'R', 'a', 'y', 'c', 'a', 's', 't', 'e', 'r', ' ', '{', '\n', +' ', ' ', 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', ' ', 'i', 'n', 'i', 't', '(', ')', ' ', '{', '}', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 'e', 't', 'P', 'o', 's', 'i', 't', 'i', 'o', 'n', '(', 'x', ',', ' ', 'y', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 'e', 't', 'A', 'n', 'g', 'l', 'e', '(', 'a', 'n', 'g', 'l', 'e', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 'e', 't', 'D', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n', 's', '(', 'w', 'i', 'd', 't', 'h', ',', ' ', 'h', 'e', 'i', 'g', 'h', 't', ')', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'd', 'r', 'a', 'w', '(', 'a', 'l', 'p', 'h', 'a', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'u', 'p', 'd', 'a', 't', 'e', '(', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'l', 'o', 'a', 'd', 'T', 'e', 'x', 't', 'u', 'r', 'e', '(', 'p', 'a', 't', 'h', ')', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'f', '_', 'p', 'u', 's', 'h', 'O', 'b', 'j', 'e', 'c', 't', '(', 'x', ',', ' ', 'y', ',', ' ', 't', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', ')', '\n', +' ', ' ', 't', 'i', 'l', 'e', '(', 'x', ',', ' ', 'y', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'W', 'o', 'r', 'l', 'd', 'T', 'i', 'l', 'e', '.', 'i', 'n', 'i', 't', '(', 't', 'h', 'i', 's', ',', ' ', 'x', ',', ' ', 'y', ')', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'o', 'b', 'j', 'e', 'c', 't', '(', 'i', 'd', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', '/', '/', ' ', 'S', 'h', 'o', 'u', 'l', 'd', ' ', 'w', 'e', ' ', 'c', 'r', 'e', 'a', 't', 'e', ' ', 'a', 'n', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 'i', 'f', ' ', 't', 'h', 'e', ' ', 'i', 'd', ' ', 'i', 's', ' ', 'n', 'o', 't', ' ', 'u', 's', 'e', 'd', '?', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'W', 'o', 'r', 'l', 'd', 'O', 'b', 'j', 'e', 'c', 't', '.', 'i', 'n', 'i', 't', '(', 't', 'h', 'i', 's', ',', ' ', 'i', 'd', ')', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'p', 'u', 's', 'h', 'O', 'b', 'j', 'e', 'c', 't', '(', 'x', ',', ' ', 'y', ',', ' ', 't', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'v', 'a', 'r', ' ', 'i', 'd', ' ', '=', ' ', 'f', '_', 'p', 'u', 's', 'h', 'O', 'b', 'j', 'e', 'c', 't', '(', 'x', ',', ' ', 'y', ',', ' ', 't', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', ')', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'W', 'o', 'r', 'l', 'd', 'O', 'b', 'j', 'e', 'c', 't', '.', 'i', 'n', 'i', 't', '(', 't', 'h', 'i', 's', ',', ' ', 'i', 'd', ')', '\n', +' ', ' ', '}', '\n', +'}', '\n', +'\n', +'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'l', 'a', 's', 's', ' ', 'W', 'o', 'r', 'l', 'd', 'O', 'b', 'j', 'e', 'c', 't', ' ', '{', '\n', +' ', ' ', 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', ' ', 'i', 'n', 'i', 't', '(', 'r', 'e', 'n', 'd', 'e', 'r', 'e', 'r', ',', ' ', 'i', 'd', ')', ' ', '{', '}', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'r', 'e', 'm', 'o', 'v', 'e', '(', ')', '\n', +'\n', +' ', ' ', '/', '/', ' ', 'T', 'O', 'D', 'O', ':', ' ', 'F', 'l', 'u', 'e', 'n', 't', ' ', 'A', 'P', 'I', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'i', 'd', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 't', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 't', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'x', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'x', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'y', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'y', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'u', 'D', 'i', 'v', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'u', 'D', 'i', 'v', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'v', 'D', 'i', 'v', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'v', 'D', 'i', 'v', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'v', 'M', 'o', 'v', 'e', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'v', 'M', 'o', 'v', 'e', '=', '(', 'v', ')', '\n', +'}', '\n', +'\n', +'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'l', 'a', 's', 's', ' ', 'W', 'o', 'r', 'l', 'd', 'T', 'i', 'l', 'e', ' ', '{', '\n', +' ', ' ', 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', ' ', 'i', 'n', 'i', 't', '(', 'r', 'e', 'n', 'd', 'e', 'r', 'e', 'r', ',', ' ', 'x', ',', ' ', 'y', ')', ' ', '{', '}', '\n', +'\n', +' ', ' ', 't', 'h', 'i', 'n', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 't', 'h', 'i', 'n', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'o', 'f', 'f', 's', 'e', 't', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'o', 'f', 'f', 's', 'e', 't', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +' ', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', ' ', '=', ' ', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'R', 'e', 'f', '.', 'i', 'n', 'i', 't', '(', 'v', 'a', 'l', 'u', 'e', ')', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'c', 'e', 'i', 'l', 'i', 'n', 'g', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'c', 'e', 'i', 'l', 'i', 'n', 'g', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'f', 'l', 'o', 'o', 'r', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'f', 'l', 'o', 'o', 'r', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 's', 'o', 'l', 'i', 'd', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 's', 'o', 'l', 'i', 'd', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 's', 't', 'a', 't', 'e', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 's', 't', 'a', 't', 'e', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'm', 'o', 'd', 'e', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'm', 'o', 'd', 'e', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'd', 'o', 'o', 'r', '(', 'v', 'a', 'l', 'u', 'e', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 'd', 'o', 'o', 'r', ' ', '=', ' ', 'v', 'a', 'l', 'u', 'e', '\n', +' ', ' ', ' ', ' ', 'r', 'e', 't', 'u', 'r', 'n', ' ', 't', 'h', 'i', 's', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', '=', '(', 'v', ')', '\n', +' ', ' ', '/', '/', ' ', 'T', 'O', 'D', 'O', ':', ' ', 'G', 'e', 't', ' ', 'w', 'a', 'l', 'l', 'T', 'e', 'x', 't', 'u', 'r', 'e', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'e', 'i', 'l', 'i', 'n', 'g', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'e', 'i', 'l', 'i', 'n', 'g', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'f', 'l', 'o', 'o', 'r', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'f', 'l', 'o', 'o', 'r', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'I', 'd', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 'o', 'l', 'i', 'd', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 'o', 'l', 'i', 'd', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'd', 'o', 'o', 'r', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'd', 'o', 'o', 'r', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 't', 'a', 't', 'e', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 's', 't', 'a', 't', 'e', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'm', 'o', 'd', 'e', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'm', 'o', 'd', 'e', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'o', 'f', 'f', 's', 'e', 't', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'o', 'f', 'f', 's', 'e', 't', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 't', 'h', 'i', 'n', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 't', 'h', 'i', 'n', '=', '(', 'v', ')', '\n', +'}', '\n', +'\n', +'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'c', 'l', 'a', 's', 's', ' ', 'T', 'e', 'x', 't', 'u', 'r', 'e', 'R', 'e', 'f', ' ', '{', '\n', +' ', ' ', 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', ' ', 'i', 'n', 'i', 't', '(', 'i', 'd', ')', ' ', '{', '}', '\n', +' ', ' ', 'c', 'o', 'n', 's', 't', 'r', 'u', 'c', 't', ' ', 'i', 'n', 'i', 't', '(', 'i', 'd', ',', ' ', 'm', 'i', 'n', ',', ' ', 'm', 'a', 'x', ')', ' ', '{', '\n', +' ', ' ', ' ', ' ', 't', 'h', 'i', 's', '.', 'm', 'i', 'n', ' ', '=', ' ', 'm', 'i', 'n', '\n', +' ', ' ', ' ', ' ', 't', 'h', 'i', 's', '.', 'm', 'a', 'x', ' ', '=', ' ', 'm', 'a', 'x', '\n', +' ', ' ', '}', '\n', +'\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'i', 'd', '=', '(', 'v', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'm', 'i', 'n', '=', '(', 'l', 'i', 's', 't', ')', '\n', +' ', ' ', 'f', 'o', 'r', 'e', 'i', 'g', 'n', ' ', 'm', 'a', 'x', '=', '(', 'l', 'i', 's', 't', ')', '\n', +'}', '\n', + }; diff --git a/examples/raycaster/res/DUMMY1.png b/examples/raycaster/res/DUMMY1.png new file mode 100644 index 00000000..e8cd5152 Binary files /dev/null and b/examples/raycaster/res/DUMMY1.png differ diff --git a/examples/raycaster/res/DUMMY2.png b/examples/raycaster/res/DUMMY2.png new file mode 100644 index 00000000..252a84f7 Binary files /dev/null and b/examples/raycaster/res/DUMMY2.png differ diff --git a/examples/raycaster/res/DUMMY3.png b/examples/raycaster/res/DUMMY3.png new file mode 100644 index 00000000..f4813623 Binary files /dev/null and b/examples/raycaster/res/DUMMY3.png differ diff --git a/examples/raycaster/res/DUMMY4.png b/examples/raycaster/res/DUMMY4.png new file mode 100644 index 00000000..22a77c32 Binary files /dev/null and b/examples/raycaster/res/DUMMY4.png differ diff --git a/examples/raycaster/res/DUMMY5.png b/examples/raycaster/res/DUMMY5.png new file mode 100644 index 00000000..84625b88 Binary files /dev/null and b/examples/raycaster/res/DUMMY5.png differ diff --git a/examples/raycaster/res/DUMMY6.png b/examples/raycaster/res/DUMMY6.png new file mode 100644 index 00000000..690d6f4a Binary files /dev/null and b/examples/raycaster/res/DUMMY6.png differ diff --git a/examples/raycaster/res/DUMMY7.png b/examples/raycaster/res/DUMMY7.png new file mode 100644 index 00000000..92eafb64 Binary files /dev/null and b/examples/raycaster/res/DUMMY7.png differ diff --git a/examples/raycaster/res/DUMMY8.png b/examples/raycaster/res/DUMMY8.png new file mode 100644 index 00000000..dd8439ba Binary files /dev/null and b/examples/raycaster/res/DUMMY8.png differ diff --git a/examples/raycaster/res/blankwall.png b/examples/raycaster/res/blankwall.png new file mode 100644 index 00000000..d1c2f28e Binary files /dev/null and b/examples/raycaster/res/blankwall.png differ diff --git a/examples/raycaster/res/ceil.png b/examples/raycaster/res/ceil.png new file mode 100644 index 00000000..8542465a Binary files /dev/null and b/examples/raycaster/res/ceil.png differ diff --git a/examples/raycaster/res/charger-floor.png b/examples/raycaster/res/charger-floor.png new file mode 100644 index 00000000..965d5762 Binary files /dev/null and b/examples/raycaster/res/charger-floor.png differ diff --git a/examples/raycaster/res/charger-walls.png b/examples/raycaster/res/charger-walls.png new file mode 100644 index 00000000..ee94bf9c Binary files /dev/null and b/examples/raycaster/res/charger-walls.png differ diff --git a/examples/raycaster/res/column.png b/examples/raycaster/res/column.png new file mode 100644 index 00000000..0c68dda7 Binary files /dev/null and b/examples/raycaster/res/column.png differ diff --git a/examples/raycaster/res/column2.png b/examples/raycaster/res/column2.png new file mode 100644 index 00000000..3d29afdb Binary files /dev/null and b/examples/raycaster/res/column2.png differ diff --git a/examples/raycaster/res/door.png b/examples/raycaster/res/door.png new file mode 100644 index 00000000..dd8bc1d1 Binary files /dev/null and b/examples/raycaster/res/door.png differ diff --git a/examples/raycaster/res/energy-bar.png b/examples/raycaster/res/energy-bar.png new file mode 100644 index 00000000..6793967f Binary files /dev/null and b/examples/raycaster/res/energy-bar.png differ diff --git a/examples/raycaster/res/floor.png b/examples/raycaster/res/floor.png new file mode 100644 index 00000000..c4777ff7 Binary files /dev/null and b/examples/raycaster/res/floor.png differ diff --git a/examples/raycaster/res/guard-test.png b/examples/raycaster/res/guard-test.png new file mode 100644 index 00000000..a9036456 Binary files /dev/null and b/examples/raycaster/res/guard-test.png differ diff --git a/examples/raycaster/res/light.png b/examples/raycaster/res/light.png new file mode 100644 index 00000000..097368bc Binary files /dev/null and b/examples/raycaster/res/light.png differ diff --git a/examples/raycaster/res/pit.png b/examples/raycaster/res/pit.png new file mode 100644 index 00000000..a9f3a0dd Binary files /dev/null and b/examples/raycaster/res/pit.png differ diff --git a/examples/raycaster/res/terminal.png b/examples/raycaster/res/terminal.png new file mode 100644 index 00000000..7abd807e Binary files /dev/null and b/examples/raycaster/res/terminal.png differ diff --git a/examples/raycaster/res/uibox.png b/examples/raycaster/res/uibox.png new file mode 100644 index 00000000..6a6ebfcc Binary files /dev/null and b/examples/raycaster/res/uibox.png differ diff --git a/examples/raycaster/res/wall1.png b/examples/raycaster/res/wall1.png new file mode 100644 index 00000000..5bc43310 Binary files /dev/null and b/examples/raycaster/res/wall1.png differ diff --git a/examples/raycaster/res/wall2.png b/examples/raycaster/res/wall2.png new file mode 100644 index 00000000..bcef18b2 Binary files /dev/null and b/examples/raycaster/res/wall2.png differ diff --git a/examples/raycaster/res/wall3.png b/examples/raycaster/res/wall3.png new file mode 100644 index 00000000..788eacf5 Binary files /dev/null and b/examples/raycaster/res/wall3.png differ diff --git a/examples/raycaster/res/wall4.png b/examples/raycaster/res/wall4.png new file mode 100644 index 00000000..1397f7a3 Binary files /dev/null and b/examples/raycaster/res/wall4.png differ diff --git a/examples/raycaster/res/walls.ase b/examples/raycaster/res/walls.ase new file mode 100644 index 00000000..542e8fbb Binary files /dev/null and b/examples/raycaster/res/walls.ase differ diff --git a/examples/raycaster/res/weaponsheet.png b/examples/raycaster/res/weaponsheet.png new file mode 100644 index 00000000..0e679020 Binary files /dev/null and b/examples/raycaster/res/weaponsheet.png differ diff --git a/examples/raycaster/sbuf.c b/examples/raycaster/sbuf.c new file mode 100644 index 00000000..09d7afa6 --- /dev/null +++ b/examples/raycaster/sbuf.c @@ -0,0 +1,264 @@ +// stretchy_buffer.h - v1.04 - public domain - nothings.org/stb +// a vector<>-like dynamic array for C +// +// version history: +// 1.04 - fix warning +// 1.03 - compile as C++ maybe +// 1.02 - tweaks to syntax for no good reason +// 1.01 - added a "common uses" documentation section +// 1.0 - fixed bug in the version I posted prematurely +// 0.9 - rewrite to try to avoid strict-aliasing optimization +// issues, but won't compile as C++ +// +// Will probably not work correctly with strict-aliasing optimizations. +// +// The idea: +// +// This implements an approximation to C++ vector<> for C, in that it +// provides a generic definition for dynamic arrays which you can +// still access in a typesafe way using arr[i] or *(arr+i). However, +// it is simply a convenience wrapper around the common idiom of +// of keeping a set of variables (in a struct or globals) which store +// - pointer to array +// - the length of the "in-use" part of the array +// - the current size of the allocated array +// +// I find it to be the single most useful non-built-in-structure when +// programming in C (hash tables a close second), but to be clear +// it lacks many of the capabilities of C++ vector<>: there is no +// range checking, the object address isn't stable (see next section +// for details), the set of methods available is small (although +// the file stb.h has another implementation of stretchy buffers +// called 'stb_arr' which provides more methods, e.g. for insertion +// and deletion). +// +// How to use: +// +// Unlike other stb header file libraries, there is no need to +// define an _IMPLEMENTATION symbol. Every #include creates as +// much implementation is needed. +// +// stretchy_buffer.h does not define any types, so you do not +// need to #include it to before defining data types that are +// stretchy buffers, only in files that *manipulate* stretchy +// buffers. +// +// If you want a stretchy buffer aka dynamic array containing +// objects of TYPE, declare such an array as: +// +// TYPE *myarray = NULL; +// +// (There is no typesafe way to distinguish between stretchy +// buffers and regular arrays/pointers; this is necessary to +// make ordinary array indexing work on these objects.) +// +// Unlike C++ vector<>, the stretchy_buffer has the same +// semantics as an object that you manually malloc and realloc. +// The pointer may relocate every time you add a new object +// to it, so you: +// +// 1. can't take long-term pointers to elements of the array +// 2. have to return the pointer from functions which might expand it +// (either as a return value or by storing it to a ptr-to-ptr) +// +// Now you can do the following things with this array: +// +// sb_free(TYPE *a) free the array +// sb_count(TYPE *a) the number of elements in the array +// sb_push(TYPE *a, TYPE v) adds v on the end of the array, a la push_back +// sb_add(TYPE *a, int n) adds n uninitialized elements at end of array & returns pointer to first added +// sb_last(TYPE *a) returns an lvalue of the last item in the array +// a[n] access the nth (counting from 0) element of the array +// +// #define STRETCHY_BUFFER_NO_SHORT_NAMES to only export +// names of the form 'stb_sb_' if you have a name that would +// otherwise collide. +// +// Note that these are all macros and many of them evaluate +// their arguments more than once, so the arguments should +// be side-effect-free. +// +// Note that 'TYPE *a' in sb_push and sb_add must be lvalues +// so that the library can overwrite the existing pointer if +// the object has to be reallocated. +// +// In an out-of-memory condition, the code will try to +// set up a null-pointer or otherwise-invalid-pointer +// exception to happen later. It's possible optimizing +// compilers could detect this write-to-null statically +// and optimize away some of the code, but it should only +// be along the failure path. Nevertheless, for more security +// in the face of such compilers, #define STRETCHY_BUFFER_OUT_OF_MEMORY +// to a statement such as assert(0) or exit(1) or something +// to force a failure when out-of-memory occurs. +// +// Common use: +// +// The main application for this is when building a list of +// things with an unknown quantity, either due to loading from +// a file or through a process which produces an unpredictable +// number. +// +// My most common idiom is something like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// SomeStruct new_one; +// new_one.whatever = whatever; +// new_one.whatup = whatup; +// new_one.foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// and various closely-related factorings of that. For example, +// you might have several functions to create/init new SomeStructs, +// and if you use the above idiom, you might prefer to make them +// return structs rather than take non-const-pointers-to-structs, +// so you can do things like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// if (case_A) { +// sb_push(arr, some_func1()); +// } else if (case_B) { +// sb_push(arr, some_func2()); +// } else { +// sb_push(arr, some_func3()); +// } +// } +// +// Note that the above relies on the fact that sb_push doesn't +// evaluate its second argument more than once. The macros do +// evaluate the *array* argument multiple times, and numeric +// arguments may be evaluated multiple times, but you can rely +// on the second argument of sb_push being evaluated only once. +// +// Of course, you don't have to store bare objects in the array; +// if you need the objects to have stable pointers, store an array +// of pointers instead: +// +// SomeStruct **arr = NULL; +// while (something) +// { +// SomeStruct *new_one = malloc(sizeof(*new_one)); +// new_one->whatever = whatever; +// new_one->whatup = whatup; +// new_one->foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// How it works: +// +// A long-standing tradition in things like malloc implementations +// is to store extra data before the beginning of the block returned +// to the user. The stretchy buffer implementation here uses the +// same trick; the current-count and current-allocation-size are +// stored before the beginning of the array returned to the user. +// (This means you can't directly free() the pointer, because the +// allocated pointer is different from the type-safe pointer provided +// to the user.) +// +// The details are trivial and implementation is straightforward; +// the main trick is in realizing in the first place that it's +// possible to do this in a generic, type-safe way in C. +// +// Contributors: +// +// Timothy Wright (github:ZenToad) +// +// LICENSE +// +// See end of file for license information. + +#ifndef STB_STRETCHY_BUFFER_H_INCLUDED +#define STB_STRETCHY_BUFFER_H_INCLUDED + +#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES +#define sb_free stb_sb_free +#define sb_push stb_sb_push +#define sb_count stb_sb_count +#define sb_add stb_sb_add +#define sb_last stb_sb_last +#endif + +#define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),0 : 0) +#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) +#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) +#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) +#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) + +#define stb__sbraw(a) ((int *) (void *) (a) - 2) +#define stb__sbm(a) stb__sbraw(a)[0] +#define stb__sbn(a) stb__sbraw(a)[1] + +#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) +#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) +#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) + +#include + +static void * stb__sbgrowf(void *arr, int increment, int itemsize) +{ + int dbl_cur = arr ? 2*stb__sbm(arr) : 0; + int min_needed = stb_sb_count(arr) + increment; + int m = dbl_cur > min_needed ? dbl_cur : min_needed; + int *p = (int *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); + if (p) { + if (!arr) + p[1] = 0; + p[0] = m; + return p+2; + } else { + #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY + STRETCHY_BUFFER_OUT_OF_MEMORY ; + #endif + return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later + } +} +#endif // STB_STRETCHY_BUFFER_H_INCLUDED + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ + diff --git a/examples/raycaster/texture.c b/examples/raycaster/texture.c new file mode 100644 index 00000000..480a0fc3 --- /dev/null +++ b/examples/raycaster/texture.c @@ -0,0 +1,42 @@ +void TEXTURE_REF_allocate(WrenVM* vm) { + TEXTURE_REF* ref = wren->setSlotNewForeign(vm, 0, 0, sizeof(TEXTURE_REF)); + ref->id = wren->getSlotDouble(vm, 1); + // TODO: other properties + ref->min = (V2){0, 0}; + ref->max = (V2){1, 1}; +} + +void TEXTURE_REF_setMin(WrenVM* vm) { + TEXTURE_REF* ref = wren->getSlotForeign(vm, 0); + wren->ensureSlots(vm, 3); + wren->getListElement(vm, 1, 0, 2); + ref->min.x = wren->getSlotDouble(vm, 2); + wren->getListElement(vm, 1, 1, 2); + ref->min.y = wren->getSlotDouble(vm, 2); +} + + +void TEXTURE_REF_setMax(WrenVM* vm) { + TEXTURE_REF* ref = wren->getSlotForeign(vm, 0); + wren->ensureSlots(vm, 3); + wren->getListElement(vm, 1, 0, 2); + ref->max.x = wren->getSlotDouble(vm, 2); + wren->getListElement(vm, 1, 1, 2); + ref->max.y = wren->getSlotDouble(vm, 2); +} + +#define TEXTURE_REF_SETTER(fieldName, method, fieldType) \ +void TEXTURE_REF_set##method(WrenVM* vm) { \ + TEXTURE_REF* ref = wren->getSlotForeign(vm, 0); \ + ref->fieldName = wren->getSlot##fieldType(vm, 1); \ +} + +TEXTURE_REF_SETTER(id, Id, Double) + + +void TEXTURE_REF_register(DOME_Context ctx) { + core->registerClass(ctx, "raycaster", "TextureRef", TEXTURE_REF_allocate, NULL); + core->registerFn(ctx, "raycaster", "TextureRef.id=(_)", TEXTURE_REF_setId); + core->registerFn(ctx, "raycaster", "TextureRef.min=(_)", TEXTURE_REF_setMin); + core->registerFn(ctx, "raycaster", "TextureRef.max=(_)", TEXTURE_REF_setMax); +} diff --git a/examples/raycaster/tile.c b/examples/raycaster/tile.c new file mode 100644 index 00000000..adae58cb --- /dev/null +++ b/examples/raycaster/tile.c @@ -0,0 +1,112 @@ + +void TILE_allocate(WrenVM* vm) { + TILE_REF* ref = wren->setSlotNewForeign(vm, 0, 0, sizeof(TILE_REF)); + WrenHandle* handle = wren->getSlotHandle(vm, 1); + ref->x = wren->getSlotDouble(vm, 2); + ref->y = wren->getSlotDouble(vm, 3); + ref->handle = handle; +} + +void TILE_finalize(void* data) { + TILE_REF* ref = data; + // This is probably a bad idea + wren->releaseHandle(vm, ref->handle); + ref->handle = NULL; +} + +#define TILE_GETTER(fieldName, method, fieldType) \ + void TILE_get##method(WrenVM* vm) { \ + TILE_REF* ref = wren->getSlotForeign(vm, 0); \ + wren->ensureSlots(vm, 3); \ + wren->setSlotHandle(vm, 2, ref->handle); \ + RENDERER* renderer = wren->getSlotForeign(vm, 2); \ + TILE tile; \ + if (renderer->map.width * renderer->map.height > 0) {\ + tile = getTileFrom(ref, renderer); \ + } else { \ + tile = VOID_TILE; \ + } \ + wren->setSlot##fieldType(vm, 0, tile.fieldName); \ +} + +#define TILE_SETTER(fieldName, method, fieldType) \ +void TILE_set##method(WrenVM* vm) { \ + TILE_REF* ref = wren->getSlotForeign(vm, 0); \ + wren->ensureSlots(vm, 3); \ + wren->setSlotHandle(vm, 2, ref->handle); \ + RENDERER* renderer = wren->getSlotForeign(vm, 2); \ + if (renderer->map.width * renderer->map.height > 0) {\ + getTileFrom(ref, renderer).fieldName = wren->getSlot##fieldType(vm, 1); \ + } \ +} + +#define TILE_SETTER_BOOL(fieldName, method) \ +void TILE_set##method(WrenVM* vm) { \ + TILE_REF* ref = wren->getSlotForeign(vm, 0); \ + wren->ensureSlots(vm, 3); \ + wren->setSlotHandle(vm, 2, ref->handle); \ + RENDERER* renderer = wren->getSlotForeign(vm, 2); \ + if (renderer->map.width * renderer->map.height > 0) {\ + if (wren->getSlotType(vm, 1) == WREN_TYPE_NULL) {\ + getTileFrom(ref, renderer).fieldName = false;\ + } else {\ + getTileFrom(ref, renderer).fieldName = wren->getSlotBool(vm, 1); \ + }\ + } \ +} + +TILE_GETTER(solid, Solid, Bool) +TILE_SETTER_BOOL(solid, Solid) +TILE_GETTER(door, Door, Bool) +TILE_SETTER_BOOL(door, Door) +TILE_GETTER(state, State, Double) +TILE_SETTER(state, State, Double) +TILE_GETTER(mode, Mode, Double) +TILE_SETTER(mode, Mode, Double) +TILE_GETTER(offset, Offset, Double) +TILE_SETTER(offset, Offset, Double) +TILE_GETTER(thin, Thin, Bool) +TILE_SETTER_BOOL(thin, Thin) +TILE_GETTER(ceiling.id, CeilingTextureId, Double) +TILE_SETTER(ceiling.id, CeilingTextureId, Double) +TILE_GETTER(floor.id, FloorTextureId, Double) +TILE_SETTER(floor.id, FloorTextureId, Double) +TILE_GETTER(wall.id, WallTextureId, Double) +TILE_SETTER(wall.id, WallTextureId, Double) + +void TILE_setWallTexture(WrenVM* vm) { + TILE_REF* ref = wren->getSlotForeign(vm, 0); + wren->ensureSlots(vm, 3); + wren->setSlotHandle(vm, 2, ref->handle); + RENDERER* renderer = wren->getSlotForeign(vm, 2); + if (renderer->map.width * renderer->map.height > 0) { + TEXTURE_REF* textureRef = wren->getSlotForeign(vm, 1); + getTileFrom(ref, renderer).wall = *textureRef; + } +} + + +void +TILE_register(DOME_Context ctx) { + core->registerClass(ctx, "raycaster", "WorldTile", TILE_allocate, TILE_finalize); + core->registerFn(ctx, "raycaster", "WorldTile.solid", TILE_getSolid); + core->registerFn(ctx, "raycaster", "WorldTile.solid=(_)", TILE_setSolid); + core->registerFn(ctx, "raycaster", "WorldTile.door", TILE_getDoor); + core->registerFn(ctx, "raycaster", "WorldTile.door=(_)", TILE_setDoor); + core->registerFn(ctx, "raycaster", "WorldTile.state", TILE_getState); + core->registerFn(ctx, "raycaster", "WorldTile.state=(_)", TILE_setState); + core->registerFn(ctx, "raycaster", "WorldTile.mode", TILE_getMode); + core->registerFn(ctx, "raycaster", "WorldTile.mode=(_)", TILE_setMode); + core->registerFn(ctx, "raycaster", "WorldTile.thin", TILE_getThin); + core->registerFn(ctx, "raycaster", "WorldTile.thin=(_)", TILE_setThin); + core->registerFn(ctx, "raycaster", "WorldTile.offset", TILE_getOffset); + core->registerFn(ctx, "raycaster", "WorldTile.offset=(_)", TILE_setOffset); + core->registerFn(ctx, "raycaster", "WorldTile.ceilingTextureId", TILE_getCeilingTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.ceilingTextureId=(_)", TILE_setCeilingTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.floorTextureId", TILE_getFloorTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.floorTextureId=(_)", TILE_setFloorTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.wallTextureId", TILE_getWallTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.wallTextureId=(_)", TILE_setWallTextureId); + core->registerFn(ctx, "raycaster", "WorldTile.wallTexture=(_)", TILE_setWallTexture); + +} diff --git a/examples/raycaster/wall.png b/examples/raycaster/wall.png new file mode 100644 index 00000000..90393a55 Binary files /dev/null and b/examples/raycaster/wall.png differ diff --git a/examples/spaceshooter/game.egg b/examples/spaceshooter/game.egg index 83c83116..99ab67c9 100644 Binary files a/examples/spaceshooter/game.egg and b/examples/spaceshooter/game.egg differ diff --git a/examples/spaceshooter/main.wren b/examples/spaceshooter/main.wren index 78c2caad..addf6cdc 100644 --- a/examples/spaceshooter/main.wren +++ b/examples/spaceshooter/main.wren @@ -45,8 +45,8 @@ class Main { } } - draw(dt) { - _state.draw(dt) + draw(alpha) { + _state.draw(alpha) var pos = Mouse.pos if (Mouse.isButtonPressed("right")) { Canvas.pset(pos.x, pos.y, Color.orange) @@ -93,8 +93,8 @@ class Star { } } - draw(dt) { - Canvas.pset(_x, _y + dt*(0.25+_s), Color.lightgray) + draw(alpha) { + Canvas.pset(_x, _y + alpha*(0.25+_s), Color.lightgray) } } @@ -119,9 +119,9 @@ class Bullet { _y = _y - 3 } - draw(dt) { + draw(alpha) { var color = Color.white - var y = _y + 3*dt + var y = _y + 3*alpha Canvas.rectfill(_x, y, 2, 2, Color.white) Canvas.rectfill(_x, y+2, 2, 4, Color.darkgray) } @@ -149,9 +149,9 @@ class Enemy { _y = _y + 1 } - draw(dt) { + draw(alpha) { if (alive) { - Canvas.draw(_image, x, y+dt) + Canvas.draw(_image, x, y+alpha) } } } @@ -359,11 +359,11 @@ class MainGame { box1.y2 > box2.y1 } - static draw(dt) { + static draw(alpha) { Canvas.cls() - __stars.each {|star| star.draw(dt) } - __enemies.each {|enemy| enemy.draw(dt) } - __bullets.each {|bullet| bullet.draw(dt) } + __stars.each {|star| star.draw(alpha) } + __enemies.each {|enemy| enemy.draw(alpha) } + __bullets.each {|bullet| bullet.draw(alpha) } __ship.draw(__t) __explosions.each {|explosion| explosion.draw() } @@ -399,7 +399,7 @@ class GameOverState { } } - static draw(dt) { + static draw(alpha) { Canvas.cls() Canvas.print("Game Over", 160-27, 120-3, Color.white) } diff --git a/examples/triangles/main.wren b/examples/triangles/main.wren new file mode 100644 index 00000000..053a7836 --- /dev/null +++ b/examples/triangles/main.wren @@ -0,0 +1,40 @@ +import "graphics" for Canvas, Color +import "dome" for Window +import "math" for Vec + +var Background = Color.rgb(200, 0, 0, 128) + +class Main { + construct new() {} + init() { + Canvas.resize(200, 200) + Window.resize(Canvas.width * 2, Canvas.height * 2) + _center = Vec.new(Canvas.width / 2, Canvas.height / 2) + + _length = 40 + _start = 0 + var angle = Num.pi * 0 / 180 + _p0 = _center + Vec.new(angle.cos, angle.sin) * _length + angle = angle + Num.pi * 120 / 180 + _p1 = _center + Vec.new(angle.cos, angle.sin) * _length + angle = angle + Num.pi * 120 / 180 + _p2 = _center + Vec.new(angle.cos, angle.sin) * _length + } + update() { + _start = _start + 1 + var angle = Num.pi * _start / 180 + _p0 = _center + Vec.new(angle.cos, angle.sin) * _length + angle = angle + Num.pi * 120 / 180 + _p1 = _center + Vec.new(angle.cos, angle.sin) * _length + angle = angle + Num.pi * 120 / 180 + _p2 = _center + Vec.new(angle.cos, angle.sin) * _length + + } + draw(dt) { + Canvas.cls() + Canvas.trianglefill(_p0.x, _p0.y, _p1.x, _p1.y, _p2.x, _p2.y, Background) + Canvas.triangle(_p0.x, _p0.y, _p1.x, _p1.y, _p2.x, _p2.y, Color.yellow) + } +} + +var Game = Main.new() diff --git a/include/dome.h b/include/dome.h index de0e7a79..4df76b14 100644 --- a/include/dome.h +++ b/include/dome.h @@ -40,12 +40,18 @@ typedef enum { API_DOME, API_WREN, - API_AUDIO + API_AUDIO, + API_CANVAS, + API_BITMAP, + API_IO } API_TYPE; #define DOME_API_VERSION 0 #define WREN_API_VERSION 0 #define AUDIO_API_VERSION 0 +#define CANVAS_API_VERSION 0 +#define BITMAP_API_VERSION 0 +#define IO_API_VERSION 0 // Opaque context pointer typedef void* DOME_Context; @@ -155,6 +161,8 @@ typedef struct { void (*lockModule)(DOME_Context ctx, const char* name); DOME_Context (*getContext)(WrenVM* vm); void (*log)(DOME_Context ctx, const char* text, ...); + WrenVM* (*getVM)(DOME_Context ctx); + const char* (*getLastError)(DOME_Context ctx); } DOME_API_v0; typedef uint64_t CHANNEL_ID; @@ -189,11 +197,59 @@ typedef struct { void* (*getData)(CHANNEL_REF ref); } AUDIO_API_v0; + +typedef union { + uint32_t value; + struct { + // Intel storage order + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + } component; +} DOME_Color; + +typedef struct { + int32_t width; + int32_t height; + int32_t channels; + DOME_Color* pixels; +} DOME_Bitmap; + +typedef enum { + DOME_DRAWMODE_BLEND = 1 +} DOME_DrawMode; + +typedef struct { + void (*pset)(DOME_Context ctx, int32_t x, int32_t y, DOME_Color color); + void (*unsafePset)(DOME_Context ctx, int32_t x, int32_t y, DOME_Color color); + DOME_Color (*pget)(DOME_Context ctx, int32_t x, int32_t y); + uint32_t (*getWidth)(DOME_Context ctx); + uint32_t (*getHeight)(DOME_Context ctx); + void (*draw)(DOME_Context ctx, DOME_Bitmap* bitmap, int32_t x, int32_t y, DOME_DrawMode mode); + void (*line)(DOME_Context ctx, int64_t x0, int64_t y0, int64_t x1, int64_t y1, DOME_Color color); + void (*rect)(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color); + void (*rectfill)(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color); +} CANVAS_API_v0; + +typedef struct { + DOME_Bitmap* (*fromFile)(DOME_Context ctx, const char* path); + DOME_Bitmap* (*fromFileInMemory)(DOME_Context ctx, void* buffer, size_t length); + DOME_Color (*pget)(DOME_Bitmap* bitmap, uint32_t x, uint32_t y); + void (*pset)(DOME_Bitmap* bitmap, uint32_t x, uint32_t y, DOME_Color color); + void (*free)(DOME_Bitmap* bitmap); +} BITMAP_API_v0; + +typedef struct { + void* (*readFile)(DOME_Context ctx, const char* path, size_t* length); +} IO_API_v0; + typedef void* (*DOME_getAPIFunction)(API_TYPE api, int version); PUBLIC_EXPORT void* DOME_getAPI(API_TYPE api, int version); // Helper macros to abstract the api->method +// These can be removed if you don't need them in your project #define DOME_registerModule(ctx, name, src) api->registerModule(ctx, name, src) #define DOME_registerClass(ctx, module, className, allocate, finalize) api->registerClass(ctx, module, className, allocate, finalize) diff --git a/include/jo_gif.h b/include/jo_gif.h deleted file mode 100644 index 038b9cdf..00000000 --- a/include/jo_gif.h +++ /dev/null @@ -1,398 +0,0 @@ -/* public domain, Simple, Minimalistic GIF writer - http://jonolick.com - * - * Quick Notes: - * Supports only 4 component input, alpha is currently ignored. (RGBX) - * - * Latest revisions: - * 1.00 (2015-11-03) initial release - * - * Basic usage: - * char *frame = new char[128*128*4]; // 4 component. RGBX format, where X is unused - * jo_gif_t gif = jo_gif_start("foo.gif", 128, 128, 0, 32); - * jo_gif_frame(&gif, frame, 4, false); // frame 1 - * jo_gif_frame(&gif, frame, 4, false); // frame 2 - * jo_gif_frame(&gif, frame, 4, false); // frame 3, ... - * jo_gif_end(&gif); - * */ - -#ifndef JO_INCLUDE_GIF_H -#define JO_INCLUDE_GIF_H - -#include - -// To get a header file for this, either cut and paste the header, -// or create jo_gif.h, #define JO_GIF_HEADER_FILE_ONLY, and -// then include jo_gif.cpp from it. - -typedef struct { - FILE *fp; - unsigned char palette[0x300]; - short width, height, repeat; - int numColors, palSize; - int frame; -} jo_gif_t; - -// width/height | the same for every frame -// repeat | 0 = loop forever, 1 = loop once, etc... -// palSize | must be power of 2 - 1. so, 255 not 256. -extern jo_gif_t jo_gif_start(const char *filename, short width, short height, short repeat, int palSize); - -// gif | the state (returned from jo_gif_start) -// rgba | the pixels -// delayCsec | amount of time in between frames (in centiseconds) -// localPalette | true if you want a unique palette generated for this frame (does not effect future frames) -extern void jo_gif_frame(jo_gif_t *gif, unsigned char *rgba, short delayCsec, bool localPalette); - -// gif | the state (returned from jo_gif_start) -extern void jo_gif_end(jo_gif_t *gif); - -#endif - -#ifndef JO_GIF_HEADER_FILE_ONLY - -#if defined(_MSC_VER) && _MSC_VER >= 0x1400 -#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen() -#endif - -#include -#include -#include - -// Based on NeuQuant algorithm -static void jo_gif_quantize(unsigned char *rgba, int rgbaSize, int sample, unsigned char *map, int numColors) { - // defs for freq and bias - const int intbiasshift = 16; /* bias for fractions */ - const int intbias = (((int) 1) << intbiasshift); - const int gammashift = 10; /* gamma = 1024 */ - const int betashift = 10; - const int beta = (intbias >> betashift); /* beta = 1/1024 */ - const int betagamma = (intbias << (gammashift - betashift)); - - // defs for decreasing radius factor - const int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ - const int radiusbias = (((int) 1) << radiusbiasshift); - const int radiusdec = 30; /* factor of 1/30 each cycle */ - - // defs for decreasing alpha factor - const int alphabiasshift = 10; /* alpha starts at 1.0 */ - const int initalpha = (((int) 1) << alphabiasshift); - - // radbias and alpharadbias used for radpower calculation - const int radbiasshift = 8; - const int radbias = (((int) 1) << radbiasshift); - const int alpharadbshift = (alphabiasshift + radbiasshift); - const int alpharadbias = (((int) 1) << alpharadbshift); - - sample = sample < 1 ? 1 : sample > 30 ? 30 : sample; - int network[256][3]; - int bias[256] = {}, freq[256]; - for(int i = 0; i < numColors; ++i) { - // Put nurons evenly through the luminance spectrum. - network[i][0] = network[i][1] = network[i][2] = (i << 12) / numColors; - freq[i] = intbias / numColors; - } - // Learn - { - const int primes[5] = {499, 491, 487, 503}; - int step = 4; - for(int i = 0; i < 4; ++i) { - if(rgbaSize > primes[i] * 4 && (rgbaSize % primes[i])) { // TODO/Error? primes[i]*4? - step = primes[i] * 4; - } - } - sample = step == 4 ? 1 : sample; - - int alphadec = 30 + ((sample - 1) / 3); - int samplepixels = rgbaSize / (4 * sample); - int delta = samplepixels / 100; - int alpha = initalpha; - delta = delta == 0 ? 1 : delta; - - int radius = (numColors >> 3) * radiusbias; - int rad = radius >> radiusbiasshift; - rad = rad <= 1 ? 0 : rad; - int radSq = rad*rad; - int radpower[32]; - for (int i = 0; i < rad; i++) { - radpower[i] = alpha * (((radSq - i * i) * radbias) / radSq); - } - - // Randomly walk through the pixels and relax neurons to the "optimal" target. - for(int i = 0, pix = 0; i < samplepixels;) { - int r = rgba[pix + 0] << 4; - int g = rgba[pix + 1] << 4; - int b = rgba[pix + 2] << 4; - int j = -1; - { - // finds closest neuron (min dist) and updates freq - // finds best neuron (min dist-bias) and returns position - // for frequently chosen neurons, freq[k] is high and bias[k] is negative - // bias[k] = gamma*((1/numColors)-freq[k]) - - int bestd = 0x7FFFFFFF, bestbiasd = 0x7FFFFFFF, bestpos = -1; - for (int k = 0; k < numColors; k++) { - int *n = network[k]; - int dist = abs(n[0] - r) + abs(n[1] - g) + abs(n[2] - b); - if (dist < bestd) { - bestd = dist; - bestpos = k; - } - int biasdist = dist - ((bias[k]) >> (intbiasshift - 4)); - if (biasdist < bestbiasd) { - bestbiasd = biasdist; - j = k; - } - int betafreq = freq[k] >> betashift; - freq[k] -= betafreq; - bias[k] += betafreq << gammashift; - } - freq[bestpos] += beta; - bias[bestpos] -= betagamma; - } - - // Move neuron j towards biased (b,g,r) by factor alpha - network[j][0] -= (network[j][0] - r) * alpha / initalpha; - network[j][1] -= (network[j][1] - g) * alpha / initalpha; - network[j][2] -= (network[j][2] - b) * alpha / initalpha; - if (rad != 0) { - // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] - int lo = j - rad; - lo = lo < -1 ? -1 : lo; - int hi = j + rad; - hi = hi > numColors ? numColors : hi; - for(int jj = j+1, m=1; jj < hi; ++jj) { - int a = radpower[m++]; - network[jj][0] -= (network[jj][0] - r) * a / alpharadbias; - network[jj][1] -= (network[jj][1] - g) * a / alpharadbias; - network[jj][2] -= (network[jj][2] - b) * a / alpharadbias; - } - for(int k = j-1, m=1; k > lo; --k) { - int a = radpower[m++]; - network[k][0] -= (network[k][0] - r) * a / alpharadbias; - network[k][1] -= (network[k][1] - g) * a / alpharadbias; - network[k][2] -= (network[k][2] - b) * a / alpharadbias; - } - } - - pix += step; - pix = pix >= rgbaSize ? pix - rgbaSize : pix; - - // every 1% of the image, move less over the following iterations. - if(++i % delta == 0) { - alpha -= alpha / alphadec; - radius -= radius / radiusdec; - rad = radius >> radiusbiasshift; - rad = rad <= 1 ? 0 : rad; - radSq = rad*rad; - for (j = 0; j < rad; j++) { - radpower[j] = alpha * ((radSq - j * j) * radbias / radSq); - } - } - } - } - // Unbias network to give byte values 0..255 - for (int i = 0; i < numColors; i++) { - map[i*3+0] = network[i][0] >>= 4; - map[i*3+1] = network[i][1] >>= 4; - map[i*3+2] = network[i][2] >>= 4; - } -} - -typedef struct { - FILE *fp; - int numBits; - unsigned char buf[256]; - unsigned char idx; - unsigned tmp; - int outBits; - int curBits; -} jo_gif_lzw_t; - -static void jo_gif_lzw_write(jo_gif_lzw_t *s, int code) { - s->outBits |= code << s->curBits; - s->curBits += s->numBits; - while(s->curBits >= 8) { - s->buf[s->idx++] = s->outBits & 255; - s->outBits >>= 8; - s->curBits -= 8; - if (s->idx >= 255) { - putc(s->idx, s->fp); - fwrite(s->buf, s->idx, 1, s->fp); - s->idx = 0; - } - } -} - -static void jo_gif_lzw_encode(unsigned char *in, int len, FILE *fp) { - jo_gif_lzw_t state = {fp, 9}; - int maxcode = 511; - - // Note: 30k stack space for dictionary =| - const int hashSize = 5003; - short codetab[hashSize]; - int hashTbl[hashSize]; - memset(hashTbl, 0xFF, sizeof(hashTbl)); - - jo_gif_lzw_write(&state, 0x100); - - int free_ent = 0x102; - int ent = *in++; -CONTINUE: - while (--len) { - int c = *in++; - int fcode = (c << 12) + ent; - int key = (c << 4) ^ ent; // xor hashing - while(hashTbl[key] >= 0) { - if(hashTbl[key] == fcode) { - ent = codetab[key]; - goto CONTINUE; - } - ++key; - key = key >= hashSize ? key - hashSize : key; - } - jo_gif_lzw_write(&state, ent); - ent = c; - if(free_ent < 4096) { - if(free_ent > maxcode) { - ++state.numBits; - if(state.numBits == 12) { - maxcode = 4096; - } else { - maxcode = (1< c ? c : a; } - -jo_gif_t jo_gif_start(const char *filename, short width, short height, short repeat, int numColors) { - numColors = numColors > 255 ? 255 : numColors < 2 ? 2 : numColors; - jo_gif_t gif = {}; - gif.width = width; - gif.height = height; - gif.repeat = repeat; - gif.numColors = numColors; - gif.palSize = log2(numColors); - - gif.fp = fopen(filename, "wb"); - if(!gif.fp) { - printf("Error: Could not WriteGif to %s\n", filename); - return gif; - } - - fwrite("GIF89a", 6, 1, gif.fp); - // Logical Screen Descriptor - fwrite(&gif.width, 2, 1, gif.fp); - fwrite(&gif.height, 2, 1, gif.fp); - putc(0xF0 | gif.palSize, gif.fp); - fwrite("\x00\x00", 2, 1, gif.fp); // bg color index (unused), aspect ratio - return gif; -} - -void jo_gif_frame(jo_gif_t *gif, unsigned char * rgba, short delayCsec, bool localPalette) { - if(!gif->fp) { - return; - } - short width = gif->width; - short height = gif->height; - int size = width * height; - - unsigned char localPalTbl[0x300]; - unsigned char *palette = gif->frame == 0 || !localPalette ? gif->palette : localPalTbl; - if(gif->frame == 0 || localPalette) { - jo_gif_quantize(rgba, size*4, 1, palette, gif->numColors); - } - - unsigned char *indexedPixels = (unsigned char *)malloc(size); - { - unsigned char *ditheredPixels = (unsigned char*)malloc(size*4); - memcpy(ditheredPixels, rgba, size*4); - for(int k = 0; k < size*4; k+=4) { - int rgb[3] = { ditheredPixels[k+0], ditheredPixels[k+1], ditheredPixels[k+2] }; - int bestd = 0x7FFFFFFF, best = -1; - // TODO: exhaustive search. do something better. - for(int i = 0; i < gif->numColors; ++i) { - int bb = palette[i*3+0]-rgb[0]; - int gg = palette[i*3+1]-rgb[1]; - int rr = palette[i*3+2]-rgb[2]; - int d = bb*bb + gg*gg + rr*rr; - if(d < bestd) { - bestd = d; - best = i; - } - } - indexedPixels[k/4] = best; - int diff[3] = { ditheredPixels[k+0] - palette[indexedPixels[k/4]*3+0], ditheredPixels[k+1] - palette[indexedPixels[k/4]*3+1], ditheredPixels[k+2] - palette[indexedPixels[k/4]*3+2] }; - // Floyd-Steinberg Error Diffusion - // TODO: Use something better -- http://caca.zoy.org/study/part3.html - if(k+4 < size*4) { - ditheredPixels[k+4+0] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+0]+(diff[0]*7/16), 0, 255); - ditheredPixels[k+4+1] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+1]+(diff[1]*7/16), 0, 255); - ditheredPixels[k+4+2] = (unsigned char)jo_gif_clamp(ditheredPixels[k+4+2]+(diff[2]*7/16), 0, 255); - } - if(k+width*4+4 < size*4) { - for(int i = 0; i < 3; ++i) { - ditheredPixels[k-4+width*4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k-4+width*4+i]+(diff[i]*3/16), 0, 255); - ditheredPixels[k+width*4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k+width*4+i]+(diff[i]*5/16), 0, 255); - ditheredPixels[k+width*4+4+i] = (unsigned char)jo_gif_clamp(ditheredPixels[k+width*4+4+i]+(diff[i]*1/16), 0, 255); - } - } - } - free(ditheredPixels); - } - if(gif->frame == 0) { - // Global Color Table - fwrite(palette, 3*(1<<(gif->palSize+1)), 1, gif->fp); - if(gif->repeat >= 0) { - // Netscape Extension - fwrite("\x21\xff\x0bNETSCAPE2.0\x03\x01", 16, 1, gif->fp); - fwrite(&gif->repeat, 2, 1, gif->fp); // loop count (extra iterations, 0=repeat forever) - putc(0, gif->fp); // block terminator - } - } - // Graphic Control Extension - fwrite("\x21\xf9\x04\x00", 4, 1, gif->fp); - fwrite(&delayCsec, 2, 1, gif->fp); // delayCsec x 1/100 sec - fwrite("\x00\x00", 2, 1, gif->fp); // transparent color index (first byte), currently unused - // Image Descriptor - fwrite("\x2c\x00\x00\x00\x00", 5, 1, gif->fp); // header, x,y - fwrite(&width, 2, 1, gif->fp); - fwrite(&height, 2, 1, gif->fp); - if (gif->frame == 0 || !localPalette) { - putc(0, gif->fp); - } else { - putc(0x80|gif->palSize, gif->fp ); - fwrite(palette, 3*(1<<(gif->palSize+1)), 1, gif->fp); - } - putc(8, gif->fp); // block terminator - jo_gif_lzw_encode(indexedPixels, size, gif->fp); - putc(0, gif->fp); // block terminator - ++gif->frame; - free(indexedPixels); -} - -void jo_gif_end(jo_gif_t *gif) { - if(!gif->fp) { - return; - } - putc(0x3b, gif->fp); // gif trailer - fclose(gif->fp); -} -#endif diff --git a/include/vendor.c b/include/vendor.c index a88b7db7..72730829 100644 --- a/include/vendor.c +++ b/include/vendor.c @@ -7,8 +7,6 @@ #include -#include - #define OPTPARSE_IMPLEMENTATION #include @@ -16,6 +14,11 @@ #include #include +#ifndef __EMSCRIPTEN__ +#include +#include +#endif + // Set up STB_IMAGE #define STBI_FAILURE_USERMSG #define STBI_NO_STDIO @@ -40,4 +43,3 @@ #define ABC_FIFO_IMPL #include - diff --git a/include/vendor.h b/include/vendor.h index 231cc437..fc7fa679 100644 --- a/include/vendor.h +++ b/include/vendor.h @@ -6,8 +6,9 @@ #include #include -#define JO_GIF_HEADER_FILE_ONLY -#include +#ifndef __EMSCRIPTEN__ +#include +#endif // Set up STB_IMAGE #define STBI_FAILURE_USERMSG diff --git a/include/whereami/whereami.c b/include/whereami/whereami.c new file mode 100644 index 00000000..390ea5fe --- /dev/null +++ b/include/whereami/whereami.c @@ -0,0 +1,796 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif +#include + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + + for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + int fd = open(path, O_RDONLY); + if (fd == -1) + { + length = -1; // retry + break; + } + + char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + char* p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + --p; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#define _DARWIN_BETTER_REALPATH +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + if (!resolved) + break; + } + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, 4, path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/include/whereami/whereami.h b/include/whereami/whereami.h new file mode 100644 index 00000000..670db54c --- /dev/null +++ b/include/whereami/whereami.h @@ -0,0 +1,67 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H diff --git a/include/wren.c b/include/wren.c new file mode 100644 index 00000000..807d4cee --- /dev/null +++ b/include/wren.c @@ -0,0 +1,13494 @@ +// MIT License +// +// Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Begin file "wren.h" +#ifndef wren_h +#define wren_h + +#include +#include +#include + +// The Wren semantic version number components. +#define WREN_VERSION_MAJOR 0 +#define WREN_VERSION_MINOR 4 +#define WREN_VERSION_PATCH 0 + +// A human-friendly string representation of the version. +#define WREN_VERSION_STRING "0.4.0" + +// A monotonically increasing numeric representation of the version number. Use +// this if you want to do range checks over versions. +#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ + WREN_VERSION_MINOR * 1000 + \ + WREN_VERSION_PATCH) + +#ifndef WREN_API + #if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) + #define WREN_API __declspec( dllexport ) + #else + #define WREN_API + #endif +#endif //WREN_API + +// A single virtual machine for executing Wren code. +// +// Wren has no global state, so all state stored by a running interpreter lives +// here. +typedef struct WrenVM WrenVM; + +// A handle to a Wren object. +// +// This lets code outside of the VM hold a persistent reference to an object. +// After a handle is acquired, and until it is released, this ensures the +// garbage collector will not reclaim the object it references. +typedef struct WrenHandle WrenHandle; + +// A generic allocation function that handles all explicit memory management +// used by Wren. It's used like so: +// +// - To allocate new memory, [memory] is NULL and [newSize] is the desired +// size. It should return the allocated memory or NULL on failure. +// +// - To attempt to grow an existing allocation, [memory] is the memory, 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] 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] will be +// zero. It should return NULL. +typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); + +// A function callable from Wren code, but implemented in C. +typedef void (*WrenForeignMethodFn)(WrenVM* vm); + +// A finalizer function for freeing resources owned by an instance of a foreign +// class. Unlike most foreign methods, finalizers do not have access to the VM +// and should not interact with it since it's in the middle of a garbage +// collection. +typedef void (*WrenFinalizerFn)(void* data); + +// Gives the host a chance to canonicalize the imported module name, +// potentially taking into account the (previously resolved) name of the module +// that contains the import. Typically, this is used to implement relative +// imports. +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, + const char* importer, const char* name); + +// Forward declare +struct WrenLoadModuleResult; + +// Called after loadModuleFn is called for module [name]. The original returned result +// is handed back to you in this callback, so that you can free memory if appropriate. +typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); + +// The result of a loadModuleFn call. +// [source] is the source code for the module, or NULL if the module is not found. +// [onComplete] an optional callback that will be called once Wren is done with the result. +typedef struct WrenLoadModuleResult +{ + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; +} WrenLoadModuleResult; + +// Loads and returns the source code for the module [name]. +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); + +// Returns a pointer to a foreign method on [className] in [module] with +// [signature]. +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, + const char* module, const char* className, bool isStatic, + const char* signature); + +// Displays a string of text to the user. +typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); + +typedef enum +{ + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, + + // The error message for a runtime error. + WREN_ERROR_RUNTIME, + + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE +} WrenErrorType; + +// Reports an error to the user. +// +// An error detected during compile time is reported by calling this once with +// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] +// where the error occurs, and the compiler's error [message]. +// +// A runtime error is reported by calling this once with [type] +// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's +// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are +// made for each line in the stack trace. Each of those has the resolved +// [module] and [line] where the method or function is defined and [message] is +// the name of the method or function. +typedef void (*WrenErrorFn)( + WrenVM* vm, WrenErrorType type, const char* module, int line, + const char* message); + +typedef struct +{ + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; +} WrenForeignClassMethods; + +// Returns a pair of pointers to the foreign methods used to allocate and +// finalize the data for instances of [className] in resolved [module]. +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( + WrenVM* vm, const char* module, const char* className); + +typedef struct +{ + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; + +} WrenConfiguration; + +typedef enum +{ + WREN_RESULT_SUCCESS, + WREN_RESULT_COMPILE_ERROR, + WREN_RESULT_RUNTIME_ERROR +} WrenInterpretResult; + +// The type of an object stored in a slot. +// +// This is not necessarily the object's *class*, but instead its low level +// representation type. +typedef enum +{ + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN +} WrenType; + +// Get the current wren version number. +// +// Can be used to range checks over versions. +WREN_API int wrenGetVersionNumber(); + +// Initializes [configuration] with all of its default values. +// +// Call this before setting the particular fields you care about. +WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); + +// Creates a new Wren virtual machine using the given [configuration]. Wren +// will copy the configuration data, so the argument passed to this can be +// freed after calling this. If [configuration] is `NULL`, uses a default +// configuration. +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); + +// Disposes of all resources is use by [vm], which was previously created by a +// call to [wrenNewVM]. +WREN_API void wrenFreeVM(WrenVM* vm); + +// Immediately run the garbage collector to free unused memory. +WREN_API void wrenCollectGarbage(WrenVM* vm); + +// Runs [source], a string of Wren source code in a new fiber in [vm] in the +// context of resolved [module]. +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, + const char* source); + +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. +// +// This handle can be used repeatedly to directly invoke that method from C +// code using [wrenCall]. +// +// When you are done with this handle, it must be released using +// [wrenReleaseHandle]. +WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); + +// Calls [method], using the receiver and arguments previously set up on the +// stack. +// +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. +// +// After this returns, you can access the return value from slot 0 on the stack. +WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); + +// Releases the reference stored in [handle]. After calling this, [handle] can +// no longer be used. +WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); + +// The following functions are intended to be called from foreign methods or +// finalizers. The interface Wren provides to a foreign method is like a +// register machine: you are given a numbered array of slots that values can be +// read from and written to. Values always live in a slot (unless explicitly +// captured using wrenGetSlotHandle(), which ensures the garbage collector can +// find them. +// +// When your foreign function is called, you are given one slot for the receiver +// and each argument to the method. The receiver is in slot 0 and the arguments +// are in increasingly numbered slots after that. You are free to read and +// write to those slots as you want. If you want more slots to use as scratch +// space, you can call wrenEnsureSlots() to add more. +// +// When your function returns, every slot except slot zero is discarded and the +// value in slot zero is used as the return value of the method. If you don't +// store a return value in that slot yourself, it will retain its previous +// value, the receiver. +// +// While Wren is dynamically typed, C is not. This means the C interface has to +// support the various types of primitive values a Wren variable can hold: bool, +// double, string, etc. If we supported this for every operation in the C API, +// there would be a combinatorial explosion of functions, like "get a +// double-valued element from a list", "insert a string key and double value +// into a map", etc. +// +// To avoid that, the only way to convert to and from a raw C value is by going +// into and out of a slot. All other functions work with values already in a +// slot. So, to add an element to a list, you put the list in one slot, and the +// element in another. Then there is a single API function wrenInsertInList() +// that takes the element out of that slot and puts it into the list. +// +// The goal of this API is to be easy to use while not compromising performance. +// The latter means it does not do type or bounds checking at runtime except +// using assertions which are generally removed from release builds. C is an +// unsafe language, so it's up to you to be careful to use it correctly. In +// return, you get a very fast FFI. + +// Returns the number of slots available to the current foreign method. +WREN_API int wrenGetSlotCount(WrenVM* vm); + +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. +// +// Does not shrink the stack if it has more than enough slots. +// +// It is an error to call this from a finalizer. +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); + +// Gets the type of the object in [slot]. +WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); + +// Reads a boolean value from [slot]. +// +// It is an error to call this if the slot does not contain a boolean value. +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); + +// Reads a byte array from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// Returns a pointer to the first byte of the array and fill [length] with the +// number of bytes in the array. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); + +// Reads a number from [slot]. +// +// It is an error to call this if the slot does not contain a number. +WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); + +// Reads a foreign object from [slot] and returns a pointer to the foreign data +// stored with it. +// +// It is an error to call this if the slot does not contain an instance of a +// foreign class. +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); + +// Reads a string from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); + +// Creates a handle for the value stored in [slot]. +// +// This will prevent the object that is referred to from being garbage collected +// until the handle is released by calling [wrenReleaseHandle()]. +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); + +// Stores the boolean [value] in [slot]. +WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); + +// Stores the array [length] of [bytes] in [slot]. +// +// The bytes are copied to a new string within Wren's heap, so you can free +// memory used by them after this is called. +WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); + +// Stores the numeric [value] in [slot]. +WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); + +// Creates a new instance of the foreign class stored in [classSlot] with [size] +// bytes of raw storage and places the resulting object in [slot]. +// +// This does not invoke the foreign class's constructor on the new instance. If +// you need that to happen, call the constructor from Wren, which will then +// call the allocator foreign method. In there, call this to create the object +// and then the constructor will be invoked when the allocator returns. +// +// Returns a pointer to the foreign object's data. +WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); + +// Stores a new empty list in [slot]. +WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); + +// Stores a new empty map in [slot]. +WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); + +// Stores null in [slot]. +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); + +// Stores the string [text] in [slot]. +// +// The [text] is copied to a new string within Wren's heap, so you can free +// memory used by it after this is called. The length is calculated using +// [strlen()]. If the string may contain any null bytes in the middle, then you +// should use [wrenSetSlotBytes()] instead. +WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); + +// Stores the value captured in [handle] in [slot]. +// +// This does not release the handle for the value. +WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); + +// Returns the number of elements in the list stored in [slot]. +WREN_API int wrenGetListCount(WrenVM* vm, int slot); + +// Reads element [index] from the list in [listSlot] and stores it in +// [elementSlot]. +WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Sets the value stored at [index] in the list at [listSlot], +// to the value from [elementSlot]. +WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Returns the number of entries in the map stored in [slot]. +WREN_API int wrenGetMapCount(WrenVM* vm, int slot); + +// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. +WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); + +// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and +// stores it in [valueSlot]. +WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Takes the value stored at [valueSlot] and inserts it into the map stored +// at [mapSlot] with key [keySlot]. +WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Removes a value from the map in [mapSlot], with the key from [keySlot], +// and place it in [removedValueSlot]. If not found, [removedValueSlot] is +// set to null, the same behaviour as the Wren Map API. +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, + int removedValueSlot); + +// Looks up the top level variable with [name] in resolved [module] and stores +// it in [slot]. +WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, + int slot); + +// Looks up the top level variable with [name] in resolved [module], +// returns false if not found. The module must be imported at the time, +// use wrenHasModule to ensure that before calling. +WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); + +// Returns true if [module] has been imported/resolved before, false if not. +WREN_API bool wrenHasModule(WrenVM* vm, const char* module); + +// Sets the current fiber to be aborted, and uses the value in [slot] as the +// runtime error object. +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); + +// Returns the user data associated with the WrenVM. +WREN_API void* wrenGetUserData(WrenVM* vm); + +// Sets user data associated with the WrenVM. +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); + +#endif +// End file "wren.h" +// Begin file "wren_debug.h" +#ifndef wren_debug_h +#define wren_debug_h + +// Begin file "wren_value.h" +#ifndef wren_value_h +#define wren_value_h + +#include +#include + +// Begin file "wren_common.h" +#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 unless __STDC_LIMIT_MACROS is defined. +#define __STDC_LIMIT_MACROS +#include + +// 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_` 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 + +// 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 + + #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 +// End file "wren_common.h" +// Begin file "wren_math.h" +#ifndef wren_math_h +#define wren_math_h + +#include +#include + +// 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 +// End file "wren_math.h" +// Begin file "wren_utils.h" +#ifndef wren_utils_h +#define wren_utils_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 +// End file "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 +// End file "wren_value.h" +// Begin file "wren_vm.h" +#ifndef wren_vm_h +#define wren_vm_h + +// Begin file "wren_compiler.h" +#ifndef wren_compiler_h +#define wren_compiler_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 +// End file "wren_compiler.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, +// Begin file "wren_opcodes.h" +// 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) +// End file "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 +// End file "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 +// End file "wren_debug.h" +// Begin file "wren_vm.c" +#include +#include + +// Begin file "wren_core.h" +#ifndef wren_core_h +#define wren_core_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 +// End file "wren_core.h" +// Begin file "wren_primitive.h" +#ifndef wren_primitive_h +#define wren_primitive_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 +// End file "wren_primitive.h" + +#if WREN_OPT_META +// Begin file "wren_opt_meta.h" +#ifndef wren_opt_meta_h +#define wren_opt_meta_h + + +// This module defines the Meta class and its associated methods. +#if WREN_OPT_META + +const char* wrenMetaSource(); +WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, + const char* className, + bool isStatic, + const char* signature); + +#endif + +#endif +// End file "wren_opt_meta.h" +#endif +#if WREN_OPT_RANDOM +// Begin file "wren_opt_random.h" +#ifndef wren_opt_random_h +#define wren_opt_random_h + + +#if WREN_OPT_RANDOM + +const char* wrenRandomSource(); +WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, + const char* module, + const char* className); +WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, + const char* className, + bool isStatic, + const char* signature); + +#endif + +#endif +// End file "wren_opt_random.h" +#endif + +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + #include + #include +#endif + +// The behavior of realloc() when the size is 0 is implementation defined. It +// may return a non-NULL pointer which must not be dereferenced but nevertheless +// should be freed. To prevent that, we avoid calling realloc() with a zero +// size. +static void* defaultReallocate(void* ptr, size_t newSize, void* _) +{ + if (newSize == 0) + { + free(ptr); + return NULL; + } + + return realloc(ptr, newSize); +} + +int wrenGetVersionNumber() +{ + return WREN_VERSION_NUMBER; +} + +void wrenInitConfiguration(WrenConfiguration* config) +{ + config->reallocateFn = defaultReallocate; + config->resolveModuleFn = NULL; + config->loadModuleFn = NULL; + config->bindForeignMethodFn = NULL; + config->bindForeignClassFn = NULL; + config->writeFn = NULL; + config->errorFn = NULL; + config->initialHeapSize = 1024 * 1024 * 10; + config->minHeapSize = 1024 * 1024; + config->heapGrowthPercent = 50; + config->userData = NULL; +} + +WrenVM* wrenNewVM(WrenConfiguration* config) +{ + WrenReallocateFn reallocate = defaultReallocate; + void* userData = NULL; + if (config != NULL) { + userData = config->userData; + reallocate = config->reallocateFn ? config->reallocateFn : defaultReallocate; + } + + WrenVM* vm = (WrenVM*)reallocate(NULL, sizeof(*vm), userData); + memset(vm, 0, sizeof(WrenVM)); + + // Copy the configuration if given one. + if (config != NULL) + { + memcpy(&vm->config, config, sizeof(WrenConfiguration)); + + // We choose to set this after copying, + // rather than modifying the user config pointer + vm->config.reallocateFn = reallocate; + } + else + { + wrenInitConfiguration(&vm->config); + } + + // TODO: Should we allocate and free this during a GC? + vm->grayCount = 0; + // TODO: Tune this. + vm->grayCapacity = 4; + vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); + vm->nextGC = vm->config.initialHeapSize; + + wrenSymbolTableInit(&vm->methodNames); + + vm->modules = wrenNewMap(vm); + wrenInitializeCore(vm); + return vm; +} + +void wrenFreeVM(WrenVM* vm) +{ + ASSERT(vm->methodNames.count > 0, "VM appears to have already been freed."); + + // Free all of the GC objects. + Obj* obj = vm->first; + while (obj != NULL) + { + Obj* next = obj->next; + wrenFreeObj(vm, obj); + obj = next; + } + + // Free up the GC gray set. + vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, 0, vm->config.userData); + + // Tell the user if they didn't free any handles. We don't want to just free + // them here because the host app may still have pointers to them that they + // may try to use. Better to tell them about the bug early. + ASSERT(vm->handles == NULL, "All handles have not been released."); + + wrenSymbolTableClear(vm, &vm->methodNames); + + DEALLOCATE(vm, vm); +} + +void wrenCollectGarbage(WrenVM* vm) +{ +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + printf("-- gc --\n"); + + size_t before = vm->bytesAllocated; + double startTime = (double)clock() / CLOCKS_PER_SEC; +#endif + + // Mark all reachable objects. + + // Reset this. As we mark objects, their size will be counted again so that + // we can track how much memory is in use without needing to know the size + // of each *freed* object. + // + // This is important because when freeing an unmarked object, we don't always + // know how much memory it is using. For example, when freeing an instance, + // we need to know its class to know how big it is, but its class may have + // already been freed. + vm->bytesAllocated = 0; + + wrenGrayObj(vm, (Obj*)vm->modules); + + // Temporary roots. + for (int i = 0; i < vm->numTempRoots; i++) + { + wrenGrayObj(vm, vm->tempRoots[i]); + } + + // The current fiber. + wrenGrayObj(vm, (Obj*)vm->fiber); + + // The handles. + for (WrenHandle* handle = vm->handles; + handle != NULL; + handle = handle->next) + { + wrenGrayValue(vm, handle->value); + } + + // Any object the compiler is using (if there is one). + if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); + + // Method names. + wrenBlackenSymbolTable(vm, &vm->methodNames); + + // Now that we have grayed the roots, do a depth-first search over all of the + // reachable objects. + wrenBlackenObjects(vm); + + // Collect the white objects. + Obj** obj = &vm->first; + while (*obj != NULL) + { + if (!((*obj)->isDark)) + { + // This object wasn't reached, so remove it from the list and free it. + Obj* unreached = *obj; + *obj = unreached->next; + wrenFreeObj(vm, unreached); + } + else + { + // This object was reached, so unmark it (for the next GC) and move on to + // the next. + (*obj)->isDark = false; + obj = &(*obj)->next; + } + } + + // Calculate the next gc point, this is the current allocation plus + // a configured percentage of the current allocation. + vm->nextGC = vm->bytesAllocated + ((vm->bytesAllocated * vm->config.heapGrowthPercent) / 100); + if (vm->nextGC < vm->config.minHeapSize) vm->nextGC = vm->config.minHeapSize; + +#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC + double elapsed = ((double)clock() / CLOCKS_PER_SEC) - startTime; + // Explicit cast because size_t has different sizes on 32-bit and 64-bit and + // we need a consistent type for the format string. + printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n", + (unsigned long)before, + (unsigned long)vm->bytesAllocated, + (unsigned long)(before - vm->bytesAllocated), + (unsigned long)vm->nextGC, + elapsed*1000.0); +#endif +} + +void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) +{ +#if WREN_DEBUG_TRACE_MEMORY + // Explicit cast because size_t has different sizes on 32-bit and 64-bit and + // we need a consistent type for the format string. + printf("reallocate %p %lu -> %lu\n", + memory, (unsigned long)oldSize, (unsigned long)newSize); +#endif + + // If new bytes are being allocated, add them to the total count. If objects + // are being completely deallocated, we don't track that (since we don't + // track the original size). Instead, that will be handled while marking + // during the next GC. + vm->bytesAllocated += newSize - oldSize; + +#if WREN_DEBUG_GC_STRESS + // Since collecting calls this function to free things, make sure we don't + // recurse. + if (newSize > 0) wrenCollectGarbage(vm); +#else + if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm); +#endif + + return vm->config.reallocateFn(memory, newSize, vm->config.userData); +} + +// Captures the local variable [local] into an [Upvalue]. If that local is +// already in an upvalue, the existing one will be used. (This is important to +// ensure that multiple closures closing over the same variable actually see +// the same variable.) Otherwise, it will create a new open upvalue and add it +// the fiber's list of upvalues. +static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) +{ + // If there are no open upvalues at all, we must need a new one. + if (fiber->openUpvalues == NULL) + { + fiber->openUpvalues = wrenNewUpvalue(vm, local); + return fiber->openUpvalues; + } + + ObjUpvalue* prevUpvalue = NULL; + ObjUpvalue* upvalue = fiber->openUpvalues; + + // Walk towards the bottom of the stack until we find a previously existing + // upvalue or pass where it should be. + while (upvalue != NULL && upvalue->value > local) + { + prevUpvalue = upvalue; + upvalue = upvalue->next; + } + + // Found an existing upvalue for this local. + if (upvalue != NULL && upvalue->value == local) return upvalue; + + // We've walked past this local on the stack, so there must not be an + // upvalue for it already. Make a new one and link it in in the right + // place to keep the list sorted. + ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); + if (prevUpvalue == NULL) + { + // The new one is the first one in the list. + fiber->openUpvalues = createdUpvalue; + } + else + { + prevUpvalue->next = createdUpvalue; + } + + createdUpvalue->next = upvalue; + return createdUpvalue; +} + +// Closes any open upvalues that have been created for stack slots at [last] +// and above. +static void closeUpvalues(ObjFiber* fiber, Value* last) +{ + while (fiber->openUpvalues != NULL && + fiber->openUpvalues->value >= last) + { + ObjUpvalue* upvalue = fiber->openUpvalues; + + // Move the value into the upvalue itself and point the upvalue to it. + upvalue->closed = *upvalue->value; + upvalue->value = &upvalue->closed; + + // Remove it from the open upvalue list. + fiber->openUpvalues = upvalue->next; + } +} + +// Looks up a foreign method in [moduleName] on [className] with [signature]. +// +// This will try the host's foreign method binder first. If that fails, it +// falls back to handling the built-in modules. +static WrenForeignMethodFn findForeignMethod(WrenVM* vm, + const char* moduleName, + const char* className, + bool isStatic, + const char* signature) +{ + WrenForeignMethodFn method = NULL; + + if (vm->config.bindForeignMethodFn != NULL) + { + method = vm->config.bindForeignMethodFn(vm, moduleName, className, isStatic, + signature); + } + + // If the host didn't provide it, see if it's an optional one. + if (method == NULL) + { +#if WREN_OPT_META + if (strcmp(moduleName, "meta") == 0) + { + method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); + } +#endif +#if WREN_OPT_RANDOM + if (strcmp(moduleName, "random") == 0) + { + method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); + } +#endif + } + + return method; +} + +// Defines [methodValue] as a method on [classObj]. +// +// Handles both foreign methods where [methodValue] is a string containing the +// method's signature and Wren methods where [methodValue] is a function. +// +// Aborts the current fiber if the method is a foreign method that could not be +// found. +static void bindMethod(WrenVM* vm, int methodType, int symbol, + ObjModule* module, ObjClass* classObj, Value methodValue) +{ + const char* className = classObj->name->value; + if (methodType == CODE_METHOD_STATIC) classObj = classObj->obj.classObj; + + Method method; + if (IS_STRING(methodValue)) + { + const char* name = AS_CSTRING(methodValue); + method.type = METHOD_FOREIGN; + method.as.foreign = findForeignMethod(vm, module->name->value, + className, + methodType == CODE_METHOD_STATIC, + name); + + if (method.as.foreign == NULL) + { + vm->fiber->error = wrenStringFormat(vm, + "Could not find foreign method '@' for class $ in module '$'.", + methodValue, classObj->name->value, module->name->value); + return; + } + } + else + { + method.as.closure = AS_CLOSURE(methodValue); + method.type = METHOD_BLOCK; + + // Patch up the bytecode now that we know the superclass. + wrenBindMethodCode(classObj, method.as.closure->fn); + } + + wrenBindMethod(vm, classObj, symbol, method); +} + +static void callForeign(WrenVM* vm, ObjFiber* fiber, + WrenForeignMethodFn foreign, int numArgs) +{ + ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); + vm->apiStack = fiber->stackTop - numArgs; + + foreign(vm); + + // Discard the stack slots for the arguments and temporaries but leave one + // for the result. + fiber->stackTop = vm->apiStack + 1; + + vm->apiStack = NULL; +} + +// Handles the current fiber having aborted because of an error. +// +// Walks the call chain of fibers, aborting each one until it hits a fiber that +// handles the error. If none do, tells the VM to stop. +static void runtimeError(WrenVM* vm) +{ + ASSERT(wrenHasError(vm->fiber), "Should only call this after an error."); + + ObjFiber* current = vm->fiber; + Value error = current->error; + + while (current != NULL) + { + // Every fiber along the call chain gets aborted with the same error. + current->error = error; + + // If the caller ran this fiber using "try", give it the error and stop. + if (current->state == FIBER_TRY) + { + // Make the caller's try method return the error message. + current->caller->stackTop[-1] = vm->fiber->error; + vm->fiber = current->caller; + return; + } + + // Otherwise, unhook the caller since we will never resume and return to it. + ObjFiber* caller = current->caller; + current->caller = NULL; + current = caller; + } + + // If we got here, nothing caught the error, so show the stack trace. + wrenDebugPrintStackTrace(vm); + vm->fiber = NULL; + vm->apiStack = NULL; +} + +// Aborts the current fiber with an appropriate method not found error for a +// method with [symbol] on [classObj]. +static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) +{ + vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", + OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value); +} + +// Looks up the previously loaded module with [name]. +// +// Returns `NULL` if no module with that name has been loaded. +static ObjModule* getModule(WrenVM* vm, Value name) +{ + Value moduleValue = wrenMapGet(vm->modules, name); + return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : NULL; +} + +static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, + bool isExpression, bool printErrors) +{ + // See if the module has already been loaded. + ObjModule* module = getModule(vm, name); + if (module == NULL) + { + module = wrenNewModule(vm, AS_STRING(name)); + + // It's possible for the wrenMapSet below to resize the modules map, + // and trigger a GC while doing so. When this happens it will collect + // the module we've just created. Once in the map it is safe. + wrenPushRoot(vm, (Obj*)module); + + // Store it in the VM's module registry so we don't load the same module + // multiple times. + wrenMapSet(vm, vm->modules, name, OBJ_VAL(module)); + + wrenPopRoot(vm); + + // Implicitly import the core module. + ObjModule* coreModule = getModule(vm, NULL_VAL); + for (int i = 0; i < coreModule->variables.count; i++) + { + wrenDefineVariable(vm, module, + coreModule->variableNames.data[i]->value, + coreModule->variableNames.data[i]->length, + coreModule->variables.data[i], NULL); + } + } + + ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors); + if (fn == NULL) + { + // TODO: Should we still store the module even if it didn't compile? + return NULL; + } + + // Functions are always wrapped in closures. + wrenPushRoot(vm, (Obj*)fn); + ObjClosure* closure = wrenNewClosure(vm, fn); + wrenPopRoot(vm); // fn. + + return closure; +} + +// Verifies that [superclassValue] is a valid object to inherit from. That +// means it must be a class and cannot be the class of any built-in type. +// +// Also validates that it doesn't result in a class with too many fields and +// the other limitations foreign classes have. +// +// If successful, returns `null`. Otherwise, returns a string for the runtime +// error message. +static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, + int numFields) +{ + // Make sure the superclass is a class. + if (!IS_CLASS(superclassValue)) + { + return wrenStringFormat(vm, + "Class '@' cannot inherit from a non-class object.", + name); + } + + // Make sure it doesn't inherit from a sealed built-in type. Primitive methods + // on these classes assume the instance is one of the other Obj___ types and + // will fail horribly if it's actually an ObjInstance. + ObjClass* superclass = AS_CLASS(superclassValue); + if (superclass == vm->classClass || + superclass == vm->fiberClass || + superclass == vm->fnClass || // Includes OBJ_CLOSURE. + superclass == vm->listClass || + superclass == vm->mapClass || + superclass == vm->rangeClass || + superclass == vm->stringClass || + superclass == vm->boolClass || + superclass == vm->nullClass || + superclass == vm->numClass) + { + return wrenStringFormat(vm, + "Class '@' cannot inherit from built-in class '@'.", + name, OBJ_VAL(superclass->name)); + } + + if (superclass->numFields == -1) + { + return wrenStringFormat(vm, + "Class '@' cannot inherit from foreign class '@'.", + name, OBJ_VAL(superclass->name)); + } + + if (numFields == -1 && superclass->numFields > 0) + { + return wrenStringFormat(vm, + "Foreign class '@' may not inherit from a class with fields.", + name); + } + + if (superclass->numFields + numFields > MAX_FIELDS) + { + return wrenStringFormat(vm, + "Class '@' may not have more than 255 fields, including inherited " + "ones.", name); + } + + return NULL_VAL; +} + +static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) +{ + WrenForeignClassMethods methods; + methods.allocate = NULL; + methods.finalize = NULL; + + // Check the optional built-in module first so the host can override it. + + if (vm->config.bindForeignClassFn != NULL) + { + methods = vm->config.bindForeignClassFn(vm, module->name->value, + classObj->name->value); + } + + // If the host didn't provide it, see if it's a built in optional module. + if (methods.allocate == NULL && methods.finalize == NULL) + { +#if WREN_OPT_RANDOM + if (strcmp(module->name->value, "random") == 0) + { + methods = wrenRandomBindForeignClass(vm, module->name->value, + classObj->name->value); + } +#endif + } + + Method method; + method.type = METHOD_FOREIGN; + + // Add the symbol even if there is no allocator so we can ensure that the + // symbol itself is always in the symbol table. + int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + if (methods.allocate != NULL) + { + method.as.foreign = methods.allocate; + wrenBindMethod(vm, classObj, symbol, method); + } + + // Add the symbol even if there is no finalizer so we can ensure that the + // symbol itself is always in the symbol table. + symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + if (methods.finalize != NULL) + { + method.as.foreign = (WrenForeignMethodFn)methods.finalize; + wrenBindMethod(vm, classObj, symbol, method); + } +} + +// Completes the process for creating a new class. +// +// The class attributes instance and the class itself should be on the +// top of the fiber's stack. +// +// This process handles moving the attribute data for a class from +// compile time to runtime, since it now has all the attributes associated +// with a class, including for methods. +static void endClass(WrenVM* vm) +{ + // Pull the attributes and class off the stack + Value attributes = vm->fiber->stackTop[-2]; + Value classValue = vm->fiber->stackTop[-1]; + + // Remove the stack items + vm->fiber->stackTop -= 2; + + ObjClass* classObj = AS_CLASS(classValue); + classObj->attributes = attributes; +} + +// Creates a new class. +// +// If [numFields] is -1, the class is a foreign class. The name and superclass +// should be on top of the fiber's stack. After calling this, the top of the +// stack will contain the new class. +// +// Aborts the current fiber if an error occurs. +static void createClass(WrenVM* vm, int numFields, ObjModule* module) +{ + // Pull the name and superclass off the stack. + Value name = vm->fiber->stackTop[-2]; + Value superclass = vm->fiber->stackTop[-1]; + + // We have two values on the stack and we are going to leave one, so discard + // the other slot. + vm->fiber->stackTop--; + + vm->fiber->error = validateSuperclass(vm, name, superclass, numFields); + if (wrenHasError(vm->fiber)) return; + + ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, + AS_STRING(name)); + vm->fiber->stackTop[-1] = OBJ_VAL(classObj); + + if (numFields == -1) bindForeignClass(vm, classObj, module); +} + +static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) +{ + ObjClass* classObj = AS_CLASS(stack[0]); + ASSERT(classObj->numFields == -1, "Class must be a foreign class."); + + // TODO: Don't look up every time. + int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); + ASSERT(symbol != -1, "Should have defined symbol."); + + ASSERT(classObj->methods.count > symbol, "Class should have allocator."); + Method* method = &classObj->methods.data[symbol]; + ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); + + // Pass the constructor arguments to the allocator as well. + ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call."); + vm->apiStack = stack; + + method->as.foreign(vm); + + vm->apiStack = NULL; +} + +void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) +{ + // TODO: Don't look up every time. + int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); + ASSERT(symbol != -1, "Should have defined symbol."); + + // If there are no finalizers, don't finalize it. + if (symbol == -1) return; + + // If the class doesn't have a finalizer, bail out. + ObjClass* classObj = foreign->obj.classObj; + if (symbol >= classObj->methods.count) return; + + Method* method = &classObj->methods.data[symbol]; + if (method->type == METHOD_NONE) return; + + ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); + + WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; + finalizer(foreign->data); +} + +// Let the host resolve an imported module name if it wants to. +static Value resolveModule(WrenVM* vm, Value name) +{ + // If the host doesn't care to resolve, leave the name alone. + if (vm->config.resolveModuleFn == NULL) return name; + + ObjFiber* fiber = vm->fiber; + ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn; + ObjString* importer = fn->module->name; + + const char* resolved = vm->config.resolveModuleFn(vm, importer->value, + AS_CSTRING(name)); + if (resolved == NULL) + { + vm->fiber->error = wrenStringFormat(vm, + "Could not resolve module '@' imported from '@'.", + name, OBJ_VAL(importer)); + return NULL_VAL; + } + + // If they resolved to the exact same string, we don't need to copy it. + if (resolved == AS_CSTRING(name)) return name; + + // Copy the string into a Wren String object. + name = wrenNewString(vm, resolved); + DEALLOCATE(vm, (char*)resolved); + return name; +} + +static Value importModule(WrenVM* vm, Value name) +{ + name = resolveModule(vm, name); + + // If the module is already loaded, we don't need to do anything. + Value existing = wrenMapGet(vm->modules, name); + if (!IS_UNDEFINED(existing)) return existing; + + wrenPushRoot(vm, AS_OBJ(name)); + + WrenLoadModuleResult result = {0}; + const char* source = NULL; + + // Let the host try to provide the module. + if (vm->config.loadModuleFn != NULL) + { + result = vm->config.loadModuleFn(vm, AS_CSTRING(name)); + } + + // If the host didn't provide it, see if it's a built in optional module. + if (result.source == NULL) + { + result.onComplete = NULL; + ObjString* nameString = AS_STRING(name); +#if WREN_OPT_META + if (strcmp(nameString->value, "meta") == 0) result.source = wrenMetaSource(); +#endif +#if WREN_OPT_RANDOM + if (strcmp(nameString->value, "random") == 0) result.source = wrenRandomSource(); +#endif + } + + if (result.source == NULL) + { + vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); + + // Now that we're done, give the result back in case there's cleanup to do. + if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); + + if (moduleClosure == NULL) + { + vm->fiber->error = wrenStringFormat(vm, + "Could not compile module '@'.", name); + wrenPopRoot(vm); // name. + return NULL_VAL; + } + + wrenPopRoot(vm); // name. + + // Return the closure that executes the module. + return OBJ_VAL(moduleClosure); +} + +static Value getModuleVariable(WrenVM* vm, ObjModule* module, + Value variableName) +{ + ObjString* variable = AS_STRING(variableName); + uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames, + variable->value, + variable->length); + + // It's a runtime error if the imported variable does not exist. + if (variableEntry != UINT32_MAX) + { + return module->variables.data[variableEntry]; + } + + vm->fiber->error = wrenStringFormat(vm, + "Could not find a variable named '@' in module '@'.", + variableName, OBJ_VAL(module->name)); + return NULL_VAL; +} + +inline static bool checkArity(WrenVM* vm, Value value, int numArgs) +{ + ASSERT(IS_CLOSURE(value), "Receiver must be a closure."); + ObjFn* fn = AS_CLOSURE(value)->fn; + + // We only care about missing arguments, not extras. The "- 1" is because + // numArgs includes the receiver, the function itself, which we don't want to + // count. + if (numArgs - 1 >= fn->arity) return true; + + vm->fiber->error = CONST_STRING(vm, "Function expects more arguments."); + return false; +} + + +// The main bytecode interpreter loop. This is where the magic happens. It is +// also, as you can imagine, highly performance critical. +static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) +{ + // Remember the current fiber so we can find it if a GC happens. + vm->fiber = fiber; + fiber->state = FIBER_ROOT; + + // Hoist these into local variables. They are accessed frequently in the loop + // but assigned less frequently. Keeping them in locals and updating them when + // a call frame has been pushed or popped gives a large speed boost. + register CallFrame* frame; + register Value* stackStart; + register uint8_t* ip; + register ObjFn* fn; + + // These macros are designed to only be invoked within this function. + #define PUSH(value) (*fiber->stackTop++ = value) + #define POP() (*(--fiber->stackTop)) + #define DROP() (fiber->stackTop--) + #define PEEK() (*(fiber->stackTop - 1)) + #define PEEK2() (*(fiber->stackTop - 2)) + #define READ_BYTE() (*ip++) + #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1])) + + // Use this before a CallFrame is pushed to store the local variables back + // into the current one. + #define STORE_FRAME() frame->ip = ip + + // Use this after a CallFrame has been pushed or popped to refresh the local + // variables. + #define LOAD_FRAME() \ + do \ + { \ + frame = &fiber->frames[fiber->numFrames - 1]; \ + stackStart = frame->stackStart; \ + ip = frame->ip; \ + fn = frame->closure->fn; \ + } while (false) + + // Terminates the current fiber with error string [error]. If another calling + // fiber is willing to catch the error, transfers control to it, otherwise + // exits the interpreter. + #define RUNTIME_ERROR() \ + do \ + { \ + STORE_FRAME(); \ + runtimeError(vm); \ + if (vm->fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ + fiber = vm->fiber; \ + LOAD_FRAME(); \ + DISPATCH(); \ + } while (false) + + #if WREN_DEBUG_TRACE_INSTRUCTIONS + // Prints the stack and instruction before each instruction is executed. + #define DEBUG_TRACE_INSTRUCTIONS() \ + do \ + { \ + wrenDumpStack(fiber); \ + wrenDumpInstruction(vm, fn, (int)(ip - fn->code.data)); \ + } while (false) + #else + #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) + #endif + + #if WREN_COMPUTED_GOTO + + static void* dispatchTable[] = { + #define OPCODE(name, _) &&code_##name, +// Begin file "wren_opcodes.h" +// 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) +// End file "wren_opcodes.h" + #undef OPCODE + }; + + #define INTERPRET_LOOP DISPATCH(); + #define CASE_CODE(name) code_##name + + #define DISPATCH() \ + do \ + { \ + DEBUG_TRACE_INSTRUCTIONS(); \ + goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ + } while (false) + + #else + + #define INTERPRET_LOOP \ + loop: \ + DEBUG_TRACE_INSTRUCTIONS(); \ + switch (instruction = (Code)READ_BYTE()) + + #define CASE_CODE(name) case CODE_##name + #define DISPATCH() goto loop + + #endif + + LOAD_FRAME(); + + Code instruction; + INTERPRET_LOOP + { + CASE_CODE(LOAD_LOCAL_0): + CASE_CODE(LOAD_LOCAL_1): + CASE_CODE(LOAD_LOCAL_2): + CASE_CODE(LOAD_LOCAL_3): + CASE_CODE(LOAD_LOCAL_4): + CASE_CODE(LOAD_LOCAL_5): + CASE_CODE(LOAD_LOCAL_6): + CASE_CODE(LOAD_LOCAL_7): + CASE_CODE(LOAD_LOCAL_8): + PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); + DISPATCH(); + + CASE_CODE(LOAD_LOCAL): + PUSH(stackStart[READ_BYTE()]); + DISPATCH(); + + CASE_CODE(LOAD_FIELD_THIS): + { + uint8_t field = READ_BYTE(); + Value receiver = stackStart[0]; + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + PUSH(instance->fields[field]); + DISPATCH(); + } + + CASE_CODE(POP): DROP(); DISPATCH(); + CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH(); + CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH(); + CASE_CODE(TRUE): PUSH(TRUE_VAL); DISPATCH(); + + CASE_CODE(STORE_LOCAL): + stackStart[READ_BYTE()] = PEEK(); + DISPATCH(); + + CASE_CODE(CONSTANT): + PUSH(fn->constants.data[READ_SHORT()]); + DISPATCH(); + + { + // The opcodes for doing method and superclass calls share a lot of code. + // However, doing an if() test in the middle of the instruction sequence + // to handle the bit that is special to super calls makes the non-super + // call path noticeably slower. + // + // Instead, we do this old school using an explicit goto to share code for + // everything at the tail end of the call-handling code that is the same + // between normal and superclass calls. + int numArgs; + int symbol; + + Value* args; + ObjClass* classObj; + + Method* method; + + 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): + // Add one for the implicit receiver argument. + numArgs = instruction - CODE_CALL_0 + 1; + symbol = READ_SHORT(); + + // The receiver is the first argument. + args = fiber->stackTop - numArgs; + classObj = wrenGetClassInline(vm, args[0]); + goto completeCall; + + 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): + // Add one for the implicit receiver argument. + numArgs = instruction - CODE_SUPER_0 + 1; + symbol = READ_SHORT(); + + // The receiver is the first argument. + args = fiber->stackTop - numArgs; + + // The superclass is stored in a constant. + classObj = AS_CLASS(fn->constants.data[READ_SHORT()]); + goto completeCall; + + completeCall: + // If the class's method table doesn't include the symbol, bail. + if (symbol >= classObj->methods.count || + (method = &classObj->methods.data[symbol])->type == METHOD_NONE) + { + methodNotFound(vm, classObj, symbol); + RUNTIME_ERROR(); + } + + switch (method->type) + { + case METHOD_PRIMITIVE: + if (method->as.primitive(vm, args)) + { + // The result is now in the first arg slot. Discard the other + // stack slots. + fiber->stackTop -= numArgs - 1; + } else { + // An error, fiber switch, or call frame change occurred. + STORE_FRAME(); + + // If we don't have a fiber to switch to, stop interpreting. + fiber = vm->fiber; + if (fiber == NULL) return WREN_RESULT_SUCCESS; + if (wrenHasError(fiber)) RUNTIME_ERROR(); + LOAD_FRAME(); + } + break; + + case METHOD_FUNCTION_CALL: + if (!checkArity(vm, args[0], numArgs)) { + RUNTIME_ERROR(); + break; + } + + STORE_FRAME(); + method->as.primitive(vm, args); + LOAD_FRAME(); + break; + + case METHOD_FOREIGN: + callForeign(vm, fiber, method->as.foreign, numArgs); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + break; + + case METHOD_BLOCK: + STORE_FRAME(); + wrenCallFunction(vm, fiber, (ObjClosure*)method->as.closure, numArgs); + LOAD_FRAME(); + break; + + case METHOD_NONE: + UNREACHABLE(); + break; + } + DISPATCH(); + } + + CASE_CODE(LOAD_UPVALUE): + { + ObjUpvalue** upvalues = frame->closure->upvalues; + PUSH(*upvalues[READ_BYTE()]->value); + DISPATCH(); + } + + CASE_CODE(STORE_UPVALUE): + { + ObjUpvalue** upvalues = frame->closure->upvalues; + *upvalues[READ_BYTE()]->value = PEEK(); + DISPATCH(); + } + + CASE_CODE(LOAD_MODULE_VAR): + PUSH(fn->module->variables.data[READ_SHORT()]); + DISPATCH(); + + CASE_CODE(STORE_MODULE_VAR): + fn->module->variables.data[READ_SHORT()] = PEEK(); + DISPATCH(); + + CASE_CODE(STORE_FIELD_THIS): + { + uint8_t field = READ_BYTE(); + Value receiver = stackStart[0]; + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + instance->fields[field] = PEEK(); + DISPATCH(); + } + + CASE_CODE(LOAD_FIELD): + { + uint8_t field = READ_BYTE(); + Value receiver = POP(); + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + PUSH(instance->fields[field]); + DISPATCH(); + } + + CASE_CODE(STORE_FIELD): + { + uint8_t field = READ_BYTE(); + Value receiver = POP(); + ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); + ObjInstance* instance = AS_INSTANCE(receiver); + ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); + instance->fields[field] = PEEK(); + DISPATCH(); + } + + CASE_CODE(JUMP): + { + uint16_t offset = READ_SHORT(); + ip += offset; + DISPATCH(); + } + + CASE_CODE(LOOP): + { + // Jump back to the top of the loop. + uint16_t offset = READ_SHORT(); + ip -= offset; + DISPATCH(); + } + + CASE_CODE(JUMP_IF): + { + uint16_t offset = READ_SHORT(); + Value condition = POP(); + + if (wrenIsFalsyValue(condition)) ip += offset; + DISPATCH(); + } + + CASE_CODE(AND): + { + uint16_t offset = READ_SHORT(); + Value condition = PEEK(); + + if (wrenIsFalsyValue(condition)) + { + // Short-circuit the right hand side. + ip += offset; + } + else + { + // Discard the condition and evaluate the right hand side. + DROP(); + } + DISPATCH(); + } + + CASE_CODE(OR): + { + uint16_t offset = READ_SHORT(); + Value condition = PEEK(); + + if (wrenIsFalsyValue(condition)) + { + // Discard the condition and evaluate the right hand side. + DROP(); + } + else + { + // Short-circuit the right hand side. + ip += offset; + } + DISPATCH(); + } + + CASE_CODE(CLOSE_UPVALUE): + // Close the upvalue for the local if we have one. + closeUpvalues(fiber, fiber->stackTop - 1); + DROP(); + DISPATCH(); + + CASE_CODE(RETURN): + { + Value result = POP(); + fiber->numFrames--; + + // Close any upvalues still in scope. + closeUpvalues(fiber, stackStart); + + // If the fiber is complete, end it. + if (fiber->numFrames == 0) + { + // See if there's another fiber to return to. If not, we're done. + if (fiber->caller == NULL) + { + // Store the final result value at the beginning of the stack so the + // C API can get it. + fiber->stack[0] = result; + fiber->stackTop = fiber->stack + 1; + return WREN_RESULT_SUCCESS; + } + + ObjFiber* resumingFiber = fiber->caller; + fiber->caller = NULL; + fiber = resumingFiber; + vm->fiber = resumingFiber; + + // Store the result in the resuming fiber. + fiber->stackTop[-1] = result; + } + else + { + // Store the result of the block in the first slot, which is where the + // caller expects it. + stackStart[0] = result; + + // Discard the stack slots for the call frame (leaving one slot for the + // result). + fiber->stackTop = frame->stackStart + 1; + } + + LOAD_FRAME(); + DISPATCH(); + } + + CASE_CODE(CONSTRUCT): + ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); + stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); + DISPATCH(); + + CASE_CODE(FOREIGN_CONSTRUCT): + ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); + createForeign(vm, fiber, stackStart); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + + CASE_CODE(CLOSURE): + { + // Create the closure and push it on the stack before creating upvalues + // so that it doesn't get collected. + ObjFn* function = AS_FN(fn->constants.data[READ_SHORT()]); + ObjClosure* closure = wrenNewClosure(vm, function); + PUSH(OBJ_VAL(closure)); + + // Capture upvalues, if any. + for (int i = 0; i < function->numUpvalues; i++) + { + uint8_t isLocal = READ_BYTE(); + uint8_t index = READ_BYTE(); + if (isLocal) + { + // Make an new upvalue to close over the parent's local variable. + closure->upvalues[i] = captureUpvalue(vm, fiber, + frame->stackStart + index); + } + else + { + // Use the same upvalue as the current call frame. + closure->upvalues[i] = frame->closure->upvalues[index]; + } + } + DISPATCH(); + } + + CASE_CODE(END_CLASS): + { + endClass(vm); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(CLASS): + { + createClass(vm, READ_BYTE(), NULL); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(FOREIGN_CLASS): + { + createClass(vm, -1, fn->module); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DISPATCH(); + } + + CASE_CODE(METHOD_INSTANCE): + CASE_CODE(METHOD_STATIC): + { + uint16_t symbol = READ_SHORT(); + ObjClass* classObj = AS_CLASS(PEEK()); + Value method = PEEK2(); + bindMethod(vm, instruction, symbol, fn->module, classObj, method); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + DROP(); + DROP(); + DISPATCH(); + } + + CASE_CODE(END_MODULE): + { + vm->lastModule = fn->module; + PUSH(NULL_VAL); + DISPATCH(); + } + + CASE_CODE(IMPORT_MODULE): + { + // Make a slot on the stack for the module's fiber to place the return + // value. It will be popped after this fiber is resumed. Store the + // imported module's closure in the slot in case a GC happens when + // invoking the closure. + PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + + // If we get a closure, call it to execute the module body. + if (IS_CLOSURE(PEEK())) + { + STORE_FRAME(); + ObjClosure* closure = AS_CLOSURE(PEEK()); + wrenCallFunction(vm, fiber, closure, 1); + LOAD_FRAME(); + } + else + { + // The module has already been loaded. Remember it so we can import + // variables from it if needed. + vm->lastModule = AS_MODULE(PEEK()); + } + + DISPATCH(); + } + + CASE_CODE(IMPORT_VARIABLE): + { + Value variable = fn->constants.data[READ_SHORT()]; + ASSERT(vm->lastModule != NULL, "Should have already imported module."); + Value result = getModuleVariable(vm, vm->lastModule, variable); + if (wrenHasError(fiber)) RUNTIME_ERROR(); + + PUSH(result); + DISPATCH(); + } + + CASE_CODE(END): + // A CODE_END should always be preceded by a CODE_RETURN. If we get here, + // the compiler generated wrong code. + UNREACHABLE(); + } + + // We should only exit this function from an explicit return from CODE_RETURN + // or a runtime error. + UNREACHABLE(); + return WREN_RESULT_RUNTIME_ERROR; + + #undef READ_BYTE + #undef READ_SHORT +} + +WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature) +{ + ASSERT(signature != NULL, "Signature cannot be NULL."); + + int signatureLength = (int)strlen(signature); + ASSERT(signatureLength > 0, "Signature cannot be empty."); + + // Count the number parameters the method expects. + int numParams = 0; + if (signature[signatureLength - 1] == ')') + { + for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) + { + if (signature[i] == '_') numParams++; + } + } + + // Count subscript arguments. + if (signature[0] == '[') + { + for (int i = 0; i < signatureLength && signature[i] != ']'; i++) + { + if (signature[i] == '_') numParams++; + } + } + + // Add the signatue to the method table. + int method = wrenSymbolTableEnsure(vm, &vm->methodNames, + signature, signatureLength); + + // Create a little stub function that assumes the arguments are on the stack + // and calls the method. + ObjFn* fn = wrenNewFunction(vm, NULL, numParams + 1); + + // Wrap the function in a closure and then in a handle. Do this here so it + // doesn't get collected as we fill it in. + WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); + value->value = OBJ_VAL(wrenNewClosure(vm, fn)); + + wrenByteBufferWrite(vm, &fn->code, (uint8_t)(CODE_CALL_0 + numParams)); + wrenByteBufferWrite(vm, &fn->code, (method >> 8) & 0xff); + wrenByteBufferWrite(vm, &fn->code, method & 0xff); + wrenByteBufferWrite(vm, &fn->code, CODE_RETURN); + wrenByteBufferWrite(vm, &fn->code, CODE_END); + wrenIntBufferFill(vm, &fn->debug->sourceLines, 0, 5); + wrenFunctionBindName(vm, fn, signature, signatureLength); + + return value; +} + +WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) +{ + ASSERT(method != NULL, "Method cannot be NULL."); + ASSERT(IS_CLOSURE(method->value), "Method must be a method handle."); + ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); + ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); + ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); + + ObjClosure* closure = AS_CLOSURE(method->value); + + ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity, + "Stack must have enough arguments for method."); + + // Clear the API stack. Now that wrenCall() has control, we no longer need + // it. We use this being non-null to tell if re-entrant calls to foreign + // methods are happening, so it's important to clear it out now so that you + // can call foreign methods from within calls to wrenCall(). + vm->apiStack = NULL; + + // Discard any extra temporary slots. We take for granted that the stub + // function has exactly one slot for each argument. + vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots]; + + wrenCallFunction(vm, vm->fiber, closure, 0); + WrenInterpretResult result = runInterpreter(vm, vm->fiber); + + // If the call didn't abort, then set up the API stack to point to the + // beginning of the stack so the host can access the call's return value. + if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack; + + return result; +} + +WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) +{ + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // Make a handle for it. + WrenHandle* handle = ALLOCATE(vm, WrenHandle); + handle->value = value; + + if (IS_OBJ(value)) wrenPopRoot(vm); + + // Add it to the front of the linked list of handles. + if (vm->handles != NULL) vm->handles->prev = handle; + handle->prev = NULL; + handle->next = vm->handles; + vm->handles = handle; + + return handle; +} + +void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) +{ + ASSERT(handle != NULL, "Handle cannot be NULL."); + + // Update the VM's head pointer if we're releasing the first handle. + if (vm->handles == handle) vm->handles = handle->next; + + // Unlink it from the list. + if (handle->prev != NULL) handle->prev->next = handle->next; + if (handle->next != NULL) handle->next->prev = handle->prev; + + // Clear it out. This isn't strictly necessary since we're going to free it, + // but it makes for easier debugging. + handle->prev = NULL; + handle->next = NULL; + handle->value = NULL_VAL; + DEALLOCATE(vm, handle); +} + +WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, + const char* source) +{ + ObjClosure* closure = wrenCompileSource(vm, module, source, false, true); + if (closure == NULL) return WREN_RESULT_COMPILE_ERROR; + + wrenPushRoot(vm, (Obj*)closure); + ObjFiber* fiber = wrenNewFiber(vm, closure); + wrenPopRoot(vm); // closure. + vm->apiStack = NULL; + + return runInterpreter(vm, fiber); +} + +ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, const char* source, + bool isExpression, bool printErrors) +{ + Value nameValue = NULL_VAL; + if (module != NULL) + { + nameValue = wrenNewString(vm, module); + wrenPushRoot(vm, AS_OBJ(nameValue)); + } + + ObjClosure* closure = compileInModule(vm, nameValue, source, + isExpression, printErrors); + + if (module != NULL) wrenPopRoot(vm); // nameValue. + return closure; +} + +Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) +{ + ObjModule* module = getModule(vm, moduleName); + if (module == NULL) + { + vm->fiber->error = wrenStringFormat(vm, "Module '@' is not loaded.", + moduleName); + return NULL_VAL; + } + + return getModuleVariable(vm, module, variableName); +} + +Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name) +{ + int symbol = wrenSymbolTableFind(&module->variableNames, name, strlen(name)); + return module->variables.data[symbol]; +} + +int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, + size_t length, int line) +{ + if (module->variables.count == MAX_MODULE_VARS) return -2; + + // Implicitly defined variables get a "value" that is the line where the + // variable is first used. We'll use that later to report an error on the + // right line. + wrenValueBufferWrite(vm, &module->variables, NUM_VAL(line)); + return wrenSymbolTableAdd(vm, &module->variableNames, name, length); +} + +int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, + size_t length, Value value, int* line) +{ + if (module->variables.count == MAX_MODULE_VARS) return -2; + + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // See if the variable is already explicitly or implicitly declared. + int symbol = wrenSymbolTableFind(&module->variableNames, name, length); + + if (symbol == -1) + { + // Brand new variable. + symbol = wrenSymbolTableAdd(vm, &module->variableNames, name, length); + wrenValueBufferWrite(vm, &module->variables, value); + } + else if (IS_NUM(module->variables.data[symbol])) + { + // An implicitly declared variable's value will always be a number. + // Now we have a real definition. + if(line) *line = (int)AS_NUM(module->variables.data[symbol]); + module->variables.data[symbol] = value; + + // If this was a localname we want to error if it was + // referenced before this definition. + if (wrenIsLocalName(name)) symbol = -3; + } + else + { + // Already explicitly declared. + symbol = -1; + } + + if (IS_OBJ(value)) wrenPopRoot(vm); + + return symbol; +} + +// TODO: Inline? +void wrenPushRoot(WrenVM* vm, Obj* obj) +{ + ASSERT(obj != NULL, "Can't root NULL."); + ASSERT(vm->numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots."); + + vm->tempRoots[vm->numTempRoots++] = obj; +} + +void wrenPopRoot(WrenVM* vm) +{ + ASSERT(vm->numTempRoots > 0, "No temporary roots to release."); + vm->numTempRoots--; +} + +int wrenGetSlotCount(WrenVM* vm) +{ + if (vm->apiStack == NULL) return 0; + + return (int)(vm->fiber->stackTop - vm->apiStack); +} + +void wrenEnsureSlots(WrenVM* vm, int numSlots) +{ + // If we don't have a fiber accessible, create one for the API to use. + if (vm->apiStack == NULL) + { + vm->fiber = wrenNewFiber(vm, NULL); + vm->apiStack = vm->fiber->stack; + } + + int currentSize = (int)(vm->fiber->stackTop - vm->apiStack); + if (currentSize >= numSlots) return; + + // Grow the stack if needed. + int needed = (int)(vm->apiStack - vm->fiber->stack) + numSlots; + wrenEnsureStack(vm, vm->fiber, needed); + + vm->fiber->stackTop = vm->apiStack + numSlots; +} + +// Ensures that [slot] is a valid index into the API's stack of slots. +static void validateApiSlot(WrenVM* vm, int slot) +{ + ASSERT(slot >= 0, "Slot cannot be negative."); + ASSERT(slot < wrenGetSlotCount(vm), "Not that many slots."); +} + +// Gets the type of the object in [slot]. +WrenType wrenGetSlotType(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + if (IS_BOOL(vm->apiStack[slot])) return WREN_TYPE_BOOL; + if (IS_NUM(vm->apiStack[slot])) return WREN_TYPE_NUM; + if (IS_FOREIGN(vm->apiStack[slot])) return WREN_TYPE_FOREIGN; + if (IS_LIST(vm->apiStack[slot])) return WREN_TYPE_LIST; + if (IS_MAP(vm->apiStack[slot])) return WREN_TYPE_MAP; + if (IS_NULL(vm->apiStack[slot])) return WREN_TYPE_NULL; + if (IS_STRING(vm->apiStack[slot])) return WREN_TYPE_STRING; + + return WREN_TYPE_UNKNOWN; +} + +bool wrenGetSlotBool(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_BOOL(vm->apiStack[slot]), "Slot must hold a bool."); + + return AS_BOOL(vm->apiStack[slot]); +} + +const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) +{ + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); + + ObjString* string = AS_STRING(vm->apiStack[slot]); + *length = string->length; + return string->value; +} + +double wrenGetSlotDouble(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_NUM(vm->apiStack[slot]), "Slot must hold a number."); + + return AS_NUM(vm->apiStack[slot]); +} + +void* wrenGetSlotForeign(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_FOREIGN(vm->apiStack[slot]), + "Slot must hold a foreign instance."); + + return AS_FOREIGN(vm->apiStack[slot])->data; +} + +const char* wrenGetSlotString(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_STRING(vm->apiStack[slot]), "Slot must hold a string."); + + return AS_CSTRING(vm->apiStack[slot]); +} + +WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + return wrenMakeHandle(vm, vm->apiStack[slot]); +} + +// Stores [value] in [slot] in the foreign call stack. +static void setSlot(WrenVM* vm, int slot, Value value) +{ + validateApiSlot(vm, slot); + vm->apiStack[slot] = value; +} + +void wrenSetSlotBool(WrenVM* vm, int slot, bool value) +{ + setSlot(vm, slot, BOOL_VAL(value)); +} + +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) +{ + ASSERT(bytes != NULL, "Byte array cannot be NULL."); + setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); +} + +void wrenSetSlotDouble(WrenVM* vm, int slot, double value) +{ + setSlot(vm, slot, NUM_VAL(value)); +} + +void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) +{ + validateApiSlot(vm, slot); + validateApiSlot(vm, classSlot); + ASSERT(IS_CLASS(vm->apiStack[classSlot]), "Slot must hold a class."); + + ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); + ASSERT(classObj->numFields == -1, "Class must be a foreign class."); + + ObjForeign* foreign = wrenNewForeign(vm, classObj, size); + vm->apiStack[slot] = OBJ_VAL(foreign); + + return (void*)foreign->data; +} + +void wrenSetSlotNewList(WrenVM* vm, int slot) +{ + setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); +} + +void wrenSetSlotNewMap(WrenVM* vm, int slot) +{ + setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); +} + +void wrenSetSlotNull(WrenVM* vm, int slot) +{ + setSlot(vm, slot, NULL_VAL); +} + +void wrenSetSlotString(WrenVM* vm, int slot, const char* text) +{ + ASSERT(text != NULL, "String cannot be NULL."); + + setSlot(vm, slot, wrenNewString(vm, text)); +} + +void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) +{ + ASSERT(handle != NULL, "Handle cannot be NULL."); + + setSlot(vm, slot, handle->value); +} + +int wrenGetListCount(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_LIST(vm->apiStack[slot]), "Slot must hold a list."); + + ValueBuffer elements = AS_LIST(vm->apiStack[slot])->elements; + return elements.count; +} + +void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) +{ + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); + + ValueBuffer elements = AS_LIST(vm->apiStack[listSlot])->elements; + + uint32_t usedIndex = wrenValidateIndex(elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); + + vm->apiStack[elementSlot] = elements.data[usedIndex]; +} + +void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) +{ + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Slot must hold a list."); + + ObjList* list = AS_LIST(vm->apiStack[listSlot]); + + uint32_t usedIndex = wrenValidateIndex(list->elements.count, index); + ASSERT(usedIndex != UINT32_MAX, "Index out of bounds."); + + list->elements.data[usedIndex] = vm->apiStack[elementSlot]; +} + +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) +{ + validateApiSlot(vm, listSlot); + validateApiSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->apiStack[listSlot]), "Must insert into a list."); + + ObjList* list = AS_LIST(vm->apiStack[listSlot]); + + // Negative indices count from the end. + // We don't use wrenValidateIndex here because insert allows 1 past the end. + if (index < 0) index = list->elements.count + 1 + index; + + ASSERT(index <= list->elements.count, "Index out of bounds."); + + wrenListInsert(vm, list, vm->apiStack[elementSlot], index); +} + +int wrenGetMapCount(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + ASSERT(IS_MAP(vm->apiStack[slot]), "Slot must hold a map."); + + ObjMap* map = AS_MAP(vm->apiStack[slot]); + return map->count; +} + +bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) +{ + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + Value key = vm->apiStack[keySlot]; + ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); + if (!validateKey(vm, key)) return false; + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value value = wrenMapGet(map, key); + + return !IS_UNDEFINED(value); +} + +void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) +{ + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + validateApiSlot(vm, valueSlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value value = wrenMapGet(map, vm->apiStack[keySlot]); + if (IS_UNDEFINED(value)) { + value = NULL_VAL; + } + + vm->apiStack[valueSlot] = value; +} + +void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) +{ + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + validateApiSlot(vm, valueSlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Must insert into a map."); + + Value key = vm->apiStack[keySlot]; + ASSERT(wrenMapIsValidKey(key), "Key must be a value type"); + + if (!validateKey(vm, key)) { + return; + } + + Value value = vm->apiStack[valueSlot]; + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + + wrenMapSet(vm, map, key, value); +} + +void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, + int removedValueSlot) +{ + validateApiSlot(vm, mapSlot); + validateApiSlot(vm, keySlot); + ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map."); + + Value key = vm->apiStack[keySlot]; + if (!validateKey(vm, key)) { + return; + } + + ObjMap* map = AS_MAP(vm->apiStack[mapSlot]); + Value removed = wrenMapRemoveKey(vm, map, key); + setSlot(vm, removedValueSlot, removed); +} + +void wrenGetVariable(WrenVM* vm, const char* module, const char* name, + int slot) +{ + ASSERT(module != NULL, "Module cannot be NULL."); + ASSERT(name != NULL, "Variable name cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + ObjModule* moduleObj = getModule(vm, moduleName); + ASSERT(moduleObj != NULL, "Could not find module."); + + wrenPopRoot(vm); // moduleName. + + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, + name, strlen(name)); + ASSERT(variableSlot != -1, "Could not find variable."); + + setSlot(vm, slot, moduleObj->variables.data[variableSlot]); +} + +bool wrenHasVariable(WrenVM* vm, const char* module, const char* name) +{ + ASSERT(module != NULL, "Module cannot be NULL."); + ASSERT(name != NULL, "Variable name cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + //We don't use wrenHasModule since we want to use the module object. + ObjModule* moduleObj = getModule(vm, moduleName); + ASSERT(moduleObj != NULL, "Could not find module."); + + wrenPopRoot(vm); // moduleName. + + int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, + name, strlen(name)); + + return variableSlot != -1; +} + +bool wrenHasModule(WrenVM* vm, const char* module) +{ + ASSERT(module != NULL, "Module cannot be NULL."); + + Value moduleName = wrenStringFormat(vm, "$", module); + wrenPushRoot(vm, AS_OBJ(moduleName)); + + ObjModule* moduleObj = getModule(vm, moduleName); + + wrenPopRoot(vm); // moduleName. + + return moduleObj != NULL; +} + +void wrenAbortFiber(WrenVM* vm, int slot) +{ + validateApiSlot(vm, slot); + vm->fiber->error = vm->apiStack[slot]; +} + +void* wrenGetUserData(WrenVM* vm) +{ + return vm->config.userData; +} + +void wrenSetUserData(WrenVM* vm, void* userData) +{ + vm->config.userData = userData; +} +// End file "wren_vm.c" +// Begin file "wren_utils.c" +#include + + +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; +} +// End file "wren_utils.c" +// Begin file "wren_core.c" +#include +#include +#include +#include +#include +#include + + +// Begin file "wren_core.wren.inc" +// 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"; +// End file "wren_core.wren.inc" + +DEF_PRIMITIVE(bool_not) +{ + RETURN_BOOL(!AS_BOOL(args[0])); +} + +DEF_PRIMITIVE(bool_toString) +{ + if (AS_BOOL(args[0])) + { + RETURN_VAL(CONST_STRING(vm, "true")); + } + else + { + RETURN_VAL(CONST_STRING(vm, "false")); + } +} + +DEF_PRIMITIVE(class_name) +{ + RETURN_OBJ(AS_CLASS(args[0])->name); +} + +DEF_PRIMITIVE(class_supertype) +{ + ObjClass* classObj = AS_CLASS(args[0]); + + // Object has no superclass. + if (classObj->superclass == NULL) RETURN_NULL; + + RETURN_OBJ(classObj->superclass); +} + +DEF_PRIMITIVE(class_toString) +{ + RETURN_OBJ(AS_CLASS(args[0])->name); +} + +DEF_PRIMITIVE(class_attributes) +{ + RETURN_VAL(AS_CLASS(args[0])->attributes); +} + +DEF_PRIMITIVE(fiber_new) +{ + if (!validateFn(vm, args[1], "Argument")) return false; + + ObjClosure* closure = AS_CLOSURE(args[1]); + if (closure->fn->arity > 1) + { + RETURN_ERROR("Function cannot take more than one parameter."); + } + + RETURN_OBJ(wrenNewFiber(vm, closure)); +} + +DEF_PRIMITIVE(fiber_abort) +{ + vm->fiber->error = args[1]; + + // If the error is explicitly null, it's not really an abort. + return IS_NULL(args[1]); +} + +// Transfer execution to [fiber] coming from the current fiber whose stack has +// [args]. +// +// [isCall] is true if [fiber] is being called and not transferred. +// +// [hasValue] is true if a value in [args] is being passed to the new fiber. +// Otherwise, `null` is implicitly being passed. +static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall, + bool hasValue, const char* verb) +{ + + if (wrenHasError(fiber)) + { + RETURN_ERROR_FMT("Cannot $ an aborted fiber.", verb); + } + + if (isCall) + { + // You can't call a called fiber, but you can transfer directly to it, + // which is why this check is gated on `isCall`. This way, after resuming a + // suspended fiber, it will run and then return to the fiber that called it + // and so on. + if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); + + if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber."); + + // Remember who ran it. + fiber->caller = vm->fiber; + } + + if (fiber->numFrames == 0) + { + RETURN_ERROR_FMT("Cannot $ a finished fiber.", verb); + } + + // When the calling fiber resumes, we'll store the result of the call in its + // stack. If the call has two arguments (the fiber and the value), we only + // need one slot for the result, so discard the other slot now. + if (hasValue) vm->fiber->stackTop--; + + if (fiber->numFrames == 1 && + fiber->frames[0].ip == fiber->frames[0].closure->fn->code.data) + { + // The fiber is being started for the first time. If its function takes a + // parameter, bind an argument to it. + if (fiber->frames[0].closure->fn->arity == 1) + { + fiber->stackTop[0] = hasValue ? args[1] : NULL_VAL; + fiber->stackTop++; + } + } + else + { + // The fiber is being resumed, make yield() or transfer() return the result. + fiber->stackTop[-1] = hasValue ? args[1] : NULL_VAL; + } + + vm->fiber = fiber; + return false; +} + +DEF_PRIMITIVE(fiber_call) +{ + return runFiber(vm, AS_FIBER(args[0]), args, true, false, "call"); +} + +DEF_PRIMITIVE(fiber_call1) +{ + return runFiber(vm, AS_FIBER(args[0]), args, true, true, "call"); +} + +DEF_PRIMITIVE(fiber_current) +{ + RETURN_OBJ(vm->fiber); +} + +DEF_PRIMITIVE(fiber_error) +{ + RETURN_VAL(AS_FIBER(args[0])->error); +} + +DEF_PRIMITIVE(fiber_isDone) +{ + ObjFiber* runFiber = AS_FIBER(args[0]); + RETURN_BOOL(runFiber->numFrames == 0 || wrenHasError(runFiber)); +} + +DEF_PRIMITIVE(fiber_suspend) +{ + // Switching to a null fiber tells the interpreter to stop and exit. + vm->fiber = NULL; + vm->apiStack = NULL; + return false; +} + +DEF_PRIMITIVE(fiber_transfer) +{ + return runFiber(vm, AS_FIBER(args[0]), args, false, false, "transfer to"); +} + +DEF_PRIMITIVE(fiber_transfer1) +{ + return runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); +} + +DEF_PRIMITIVE(fiber_transferError) +{ + runFiber(vm, AS_FIBER(args[0]), args, false, true, "transfer to"); + vm->fiber->error = args[1]; + return false; +} + +DEF_PRIMITIVE(fiber_try) +{ + runFiber(vm, AS_FIBER(args[0]), args, true, false, "try"); + + // If we're switching to a valid fiber to try, remember that we're trying it. + if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; + return false; +} + +DEF_PRIMITIVE(fiber_try1) +{ + runFiber(vm, AS_FIBER(args[0]), args, true, true, "try"); + + // If we're switching to a valid fiber to try, remember that we're trying it. + if (!wrenHasError(vm->fiber)) vm->fiber->state = FIBER_TRY; + return false; +} + +DEF_PRIMITIVE(fiber_yield) +{ + ObjFiber* current = vm->fiber; + vm->fiber = current->caller; + + // Unhook this fiber from the one that called it. + current->caller = NULL; + current->state = FIBER_OTHER; + + if (vm->fiber != NULL) + { + // Make the caller's run method return null. + vm->fiber->stackTop[-1] = NULL_VAL; + } + + return false; +} + +DEF_PRIMITIVE(fiber_yield1) +{ + ObjFiber* current = vm->fiber; + vm->fiber = current->caller; + + // Unhook this fiber from the one that called it. + current->caller = NULL; + current->state = FIBER_OTHER; + + if (vm->fiber != NULL) + { + // Make the caller's run method return the argument passed to yield. + vm->fiber->stackTop[-1] = args[1]; + + // When the yielding fiber resumes, we'll store the result of the yield + // call in its stack. Since Fiber.yield(value) has two arguments (the Fiber + // class and the value) and we only need one slot for the result, discard + // the other slot now. + current->stackTop--; + } + + return false; +} + +DEF_PRIMITIVE(fn_new) +{ + if (!validateFn(vm, args[1], "Argument")) return false; + + // The block argument is already a function, so just return it. + RETURN_VAL(args[1]); +} + +DEF_PRIMITIVE(fn_arity) +{ + RETURN_NUM(AS_CLOSURE(args[0])->fn->arity); +} + +static void call_fn(WrenVM* vm, Value* args, int numArgs) +{ + // +1 to include the function itself. + wrenCallFunction(vm, vm->fiber, AS_CLOSURE(args[0]), numArgs + 1); +} + +#define DEF_FN_CALL(numArgs) \ + DEF_PRIMITIVE(fn_call##numArgs) \ + { \ + call_fn(vm, args, numArgs); \ + return false; \ + } + +DEF_FN_CALL(0) +DEF_FN_CALL(1) +DEF_FN_CALL(2) +DEF_FN_CALL(3) +DEF_FN_CALL(4) +DEF_FN_CALL(5) +DEF_FN_CALL(6) +DEF_FN_CALL(7) +DEF_FN_CALL(8) +DEF_FN_CALL(9) +DEF_FN_CALL(10) +DEF_FN_CALL(11) +DEF_FN_CALL(12) +DEF_FN_CALL(13) +DEF_FN_CALL(14) +DEF_FN_CALL(15) +DEF_FN_CALL(16) + +DEF_PRIMITIVE(fn_toString) +{ + RETURN_VAL(CONST_STRING(vm, "")); +} + +// Creates a new list of size args[1], with all elements initialized to args[2]. +DEF_PRIMITIVE(list_filled) +{ + if (!validateInt(vm, args[1], "Size")) return false; + if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative."); + + uint32_t size = (uint32_t)AS_NUM(args[1]); + ObjList* list = wrenNewList(vm, size); + + for (uint32_t i = 0; i < size; i++) + { + list->elements.data[i] = args[2]; + } + + RETURN_OBJ(list); +} + +DEF_PRIMITIVE(list_new) +{ + RETURN_OBJ(wrenNewList(vm, 0)); +} + +DEF_PRIMITIVE(list_add) +{ + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); + RETURN_VAL(args[1]); +} + +// Adds an element to the list and then returns the list itself. This is called +// by the compiler when compiling list literals instead of using add() to +// minimize stack churn. +DEF_PRIMITIVE(list_addCore) +{ + wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]); + + // Return the list. + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(list_clear) +{ + wrenValueBufferClear(vm, &AS_LIST(args[0])->elements); + RETURN_NULL; +} + +DEF_PRIMITIVE(list_count) +{ + RETURN_NUM(AS_LIST(args[0])->elements.count); +} + +DEF_PRIMITIVE(list_insert) +{ + ObjList* list = AS_LIST(args[0]); + + // count + 1 here so you can "insert" at the very end. + uint32_t index = validateIndex(vm, args[1], list->elements.count + 1, + "Index"); + if (index == UINT32_MAX) return false; + + wrenListInsert(vm, list, args[2], index); + RETURN_VAL(args[2]); +} + +DEF_PRIMITIVE(list_iterate) +{ + ObjList* list = AS_LIST(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) + { + if (list->elements.count == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + // Stop if we're out of bounds. + double index = AS_NUM(args[1]); + if (index < 0 || index >= list->elements.count - 1) RETURN_FALSE; + + // Otherwise, move to the next index. + RETURN_NUM(index + 1); +} + +DEF_PRIMITIVE(list_iteratorValue) +{ + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Iterator"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(list->elements.data[index]); +} + +DEF_PRIMITIVE(list_removeAt) +{ + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(wrenListRemoveAt(vm, list, index)); +} + +DEF_PRIMITIVE(list_removeValue) { + ObjList* list = AS_LIST(args[0]); + int index = wrenListIndexOf(vm, list, args[1]); + if(index == -1) RETURN_NULL; + RETURN_VAL(wrenListRemoveAt(vm, list, index)); +} + +DEF_PRIMITIVE(list_indexOf) +{ + ObjList* list = AS_LIST(args[0]); + RETURN_NUM(wrenListIndexOf(vm, list, args[1])); +} + +DEF_PRIMITIVE(list_swap) +{ + ObjList* list = AS_LIST(args[0]); + uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0"); + if (indexA == UINT32_MAX) return false; + uint32_t indexB = validateIndex(vm, args[2], list->elements.count, "Index 1"); + if (indexB == UINT32_MAX) return false; + + Value a = list->elements.data[indexA]; + list->elements.data[indexA] = list->elements.data[indexB]; + list->elements.data[indexB] = a; + + RETURN_NULL; +} + +DEF_PRIMITIVE(list_subscript) +{ + ObjList* list = AS_LIST(args[0]); + + if (IS_NUM(args[1])) + { + uint32_t index = validateIndex(vm, args[1], list->elements.count, + "Subscript"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(list->elements.data[index]); + } + + if (!IS_RANGE(args[1])) + { + RETURN_ERROR("Subscript must be a number or a range."); + } + + int step; + uint32_t count = list->elements.count; + uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); + if (start == UINT32_MAX) return false; + + ObjList* result = wrenNewList(vm, count); + for (uint32_t i = 0; i < count; i++) + { + result->elements.data[i] = list->elements.data[start + i * step]; + } + + RETURN_OBJ(result); +} + +DEF_PRIMITIVE(list_subscriptSetter) +{ + ObjList* list = AS_LIST(args[0]); + uint32_t index = validateIndex(vm, args[1], list->elements.count, + "Subscript"); + if (index == UINT32_MAX) return false; + + list->elements.data[index] = args[2]; + RETURN_VAL(args[2]); +} + +DEF_PRIMITIVE(map_new) +{ + RETURN_OBJ(wrenNewMap(vm)); +} + +DEF_PRIMITIVE(map_subscript) +{ + if (!validateKey(vm, args[1])) return false; + + ObjMap* map = AS_MAP(args[0]); + Value value = wrenMapGet(map, args[1]); + if (IS_UNDEFINED(value)) RETURN_NULL; + + RETURN_VAL(value); +} + +DEF_PRIMITIVE(map_subscriptSetter) +{ + if (!validateKey(vm, args[1])) return false; + + wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); + RETURN_VAL(args[2]); +} + +// Adds an entry to the map and then returns the map itself. This is called by +// the compiler when compiling map literals instead of using [_]=(_) to +// minimize stack churn. +DEF_PRIMITIVE(map_addCore) +{ + if (!validateKey(vm, args[1])) return false; + + wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); + + // Return the map itself. + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(map_clear) +{ + wrenMapClear(vm, AS_MAP(args[0])); + RETURN_NULL; +} + +DEF_PRIMITIVE(map_containsKey) +{ + if (!validateKey(vm, args[1])) return false; + + RETURN_BOOL(!IS_UNDEFINED(wrenMapGet(AS_MAP(args[0]), args[1]))); +} + +DEF_PRIMITIVE(map_count) +{ + RETURN_NUM(AS_MAP(args[0])->count); +} + +DEF_PRIMITIVE(map_iterate) +{ + ObjMap* map = AS_MAP(args[0]); + + if (map->count == 0) RETURN_FALSE; + + // If we're starting the iteration, start at the first used entry. + uint32_t index = 0; + + // Otherwise, start one past the last entry we stopped at. + if (!IS_NULL(args[1])) + { + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + index = (uint32_t)AS_NUM(args[1]); + + if (index >= map->capacity) RETURN_FALSE; + + // Advance the iterator. + index++; + } + + // Find a used entry, if any. + for (; index < map->capacity; index++) + { + if (!IS_UNDEFINED(map->entries[index].key)) RETURN_NUM(index); + } + + // If we get here, walked all of the entries. + RETURN_FALSE; +} + +DEF_PRIMITIVE(map_remove) +{ + if (!validateKey(vm, args[1])) return false; + + RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1])); +} + +DEF_PRIMITIVE(map_keyIteratorValue) +{ + ObjMap* map = AS_MAP(args[0]); + uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); + if (index == UINT32_MAX) return false; + + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) + { + RETURN_ERROR("Invalid map iterator."); + } + + RETURN_VAL(entry->key); +} + +DEF_PRIMITIVE(map_valueIteratorValue) +{ + ObjMap* map = AS_MAP(args[0]); + uint32_t index = validateIndex(vm, args[1], map->capacity, "Iterator"); + if (index == UINT32_MAX) return false; + + MapEntry* entry = &map->entries[index]; + if (IS_UNDEFINED(entry->key)) + { + RETURN_ERROR("Invalid map iterator."); + } + + RETURN_VAL(entry->value); +} + +DEF_PRIMITIVE(null_not) +{ + RETURN_VAL(TRUE_VAL); +} + +DEF_PRIMITIVE(null_toString) +{ + RETURN_VAL(CONST_STRING(vm, "null")); +} + +DEF_PRIMITIVE(num_fromString) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[1]); + + // Corner case: Can't parse an empty string. + if (string->length == 0) RETURN_NULL; + + errno = 0; + char* end; + double number = strtod(string->value, &end); + + // Skip past any trailing whitespace. + while (*end != '\0' && isspace((unsigned char)*end)) end++; + + if (errno == ERANGE) RETURN_ERROR("Number literal is too large."); + + // We must have consumed the entire string. Otherwise, it contains non-number + // characters and we can't parse it. + if (end < string->value + string->length) RETURN_NULL; + + RETURN_NUM(number); +} + +// Defines a primitive on Num that calls infix [op] and returns [type]. +#define DEF_NUM_CONSTANT(name, value) \ + DEF_PRIMITIVE(num_##name) \ + { \ + RETURN_NUM(value); \ + } + +DEF_NUM_CONSTANT(infinity, INFINITY) +DEF_NUM_CONSTANT(nan, WREN_DOUBLE_NAN) +DEF_NUM_CONSTANT(pi, 3.14159265358979323846264338327950288) +DEF_NUM_CONSTANT(tau, 6.28318530717958647692528676655900577) + +DEF_NUM_CONSTANT(largest, DBL_MAX) +DEF_NUM_CONSTANT(smallest, DBL_MIN) + +DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0) +DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0) + +// Defines a primitive on Num that calls infix [op] and returns [type]. +#define DEF_NUM_INFIX(name, op, type) \ + DEF_PRIMITIVE(num_##name) \ + { \ + if (!validateNum(vm, args[1], "Right operand")) return false; \ + RETURN_##type(AS_NUM(args[0]) op AS_NUM(args[1])); \ + } + +DEF_NUM_INFIX(minus, -, NUM) +DEF_NUM_INFIX(plus, +, NUM) +DEF_NUM_INFIX(multiply, *, NUM) +DEF_NUM_INFIX(divide, /, NUM) +DEF_NUM_INFIX(lt, <, BOOL) +DEF_NUM_INFIX(gt, >, BOOL) +DEF_NUM_INFIX(lte, <=, BOOL) +DEF_NUM_INFIX(gte, >=, BOOL) + +// Defines a primitive on Num that call infix bitwise [op]. +#define DEF_NUM_BITWISE(name, op) \ + DEF_PRIMITIVE(num_bitwise##name) \ + { \ + if (!validateNum(vm, args[1], "Right operand")) return false; \ + uint32_t left = (uint32_t)AS_NUM(args[0]); \ + uint32_t right = (uint32_t)AS_NUM(args[1]); \ + RETURN_NUM(left op right); \ + } + +DEF_NUM_BITWISE(And, &) +DEF_NUM_BITWISE(Or, |) +DEF_NUM_BITWISE(Xor, ^) +DEF_NUM_BITWISE(LeftShift, <<) +DEF_NUM_BITWISE(RightShift, >>) + +// Defines a primitive method on Num that returns the result of [fn]. +#define DEF_NUM_FN(name, fn) \ + DEF_PRIMITIVE(num_##name) \ + { \ + RETURN_NUM(fn(AS_NUM(args[0]))); \ + } + +DEF_NUM_FN(abs, fabs) +DEF_NUM_FN(acos, acos) +DEF_NUM_FN(asin, asin) +DEF_NUM_FN(atan, atan) +DEF_NUM_FN(cbrt, cbrt) +DEF_NUM_FN(ceil, ceil) +DEF_NUM_FN(cos, cos) +DEF_NUM_FN(floor, floor) +DEF_NUM_FN(negate, -) +DEF_NUM_FN(round, round) +DEF_NUM_FN(sin, sin) +DEF_NUM_FN(sqrt, sqrt) +DEF_NUM_FN(tan, tan) +DEF_NUM_FN(log, log) +DEF_NUM_FN(log2, log2) +DEF_NUM_FN(exp, exp) + +DEF_PRIMITIVE(num_mod) +{ + if (!validateNum(vm, args[1], "Right operand")) return false; + RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_eqeq) +{ + if (!IS_NUM(args[1])) RETURN_FALSE; + RETURN_BOOL(AS_NUM(args[0]) == AS_NUM(args[1])); +} + +DEF_PRIMITIVE(num_bangeq) +{ + if (!IS_NUM(args[1])) RETURN_TRUE; + RETURN_BOOL(AS_NUM(args[0]) != AS_NUM(args[1])); +} + +DEF_PRIMITIVE(num_bitwiseNot) +{ + // Bitwise operators always work on 32-bit unsigned ints. + RETURN_NUM(~(uint32_t)AS_NUM(args[0])); +} + +DEF_PRIMITIVE(num_dotDot) +{ + if (!validateNum(vm, args[1], "Right hand side of range")) return false; + + double from = AS_NUM(args[0]); + double to = AS_NUM(args[1]); + RETURN_VAL(wrenNewRange(vm, from, to, true)); +} + +DEF_PRIMITIVE(num_dotDotDot) +{ + if (!validateNum(vm, args[1], "Right hand side of range")) return false; + + double from = AS_NUM(args[0]); + double to = AS_NUM(args[1]); + RETURN_VAL(wrenNewRange(vm, from, to, false)); +} + +DEF_PRIMITIVE(num_atan2) +{ + if (!validateNum(vm, args[1], "x value")) return false; + + RETURN_NUM(atan2(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_min) +{ + if (!validateNum(vm, args[1], "Other value")) return false; + + double value = AS_NUM(args[0]); + double other = AS_NUM(args[1]); + RETURN_NUM(value <= other ? value : other); +} + +DEF_PRIMITIVE(num_max) +{ + if (!validateNum(vm, args[1], "Other value")) return false; + + double value = AS_NUM(args[0]); + double other = AS_NUM(args[1]); + RETURN_NUM(value > other ? value : other); +} + +DEF_PRIMITIVE(num_clamp) +{ + if (!validateNum(vm, args[1], "Min value")) return false; + if (!validateNum(vm, args[2], "Max value")) return false; + + double value = AS_NUM(args[0]); + double min = AS_NUM(args[1]); + double max = AS_NUM(args[2]); + double result = (value < min) ? min : ((value > max) ? max : value); + RETURN_NUM(result); +} + +DEF_PRIMITIVE(num_pow) +{ + if (!validateNum(vm, args[1], "Power value")) return false; + + RETURN_NUM(pow(AS_NUM(args[0]), AS_NUM(args[1]))); +} + +DEF_PRIMITIVE(num_fraction) +{ + double unused; + RETURN_NUM(modf(AS_NUM(args[0]) , &unused)); +} + +DEF_PRIMITIVE(num_isInfinity) +{ + RETURN_BOOL(isinf(AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_isInteger) +{ + double value = AS_NUM(args[0]); + if (isnan(value) || isinf(value)) RETURN_FALSE; + RETURN_BOOL(trunc(value) == value); +} + +DEF_PRIMITIVE(num_isNan) +{ + RETURN_BOOL(isnan(AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_sign) +{ + double value = AS_NUM(args[0]); + if (value > 0) + { + RETURN_NUM(1); + } + else if (value < 0) + { + RETURN_NUM(-1); + } + else + { + RETURN_NUM(0); + } +} + +DEF_PRIMITIVE(num_toString) +{ + RETURN_VAL(wrenNumToString(vm, AS_NUM(args[0]))); +} + +DEF_PRIMITIVE(num_truncate) +{ + double integer; + modf(AS_NUM(args[0]) , &integer); + RETURN_NUM(integer); +} + +DEF_PRIMITIVE(object_same) +{ + RETURN_BOOL(wrenValuesEqual(args[1], args[2])); +} + +DEF_PRIMITIVE(object_not) +{ + RETURN_VAL(FALSE_VAL); +} + +DEF_PRIMITIVE(object_eqeq) +{ + RETURN_BOOL(wrenValuesEqual(args[0], args[1])); +} + +DEF_PRIMITIVE(object_bangeq) +{ + RETURN_BOOL(!wrenValuesEqual(args[0], args[1])); +} + +DEF_PRIMITIVE(object_is) +{ + if (!IS_CLASS(args[1])) + { + RETURN_ERROR("Right operand must be a class."); + } + + ObjClass *classObj = wrenGetClass(vm, args[0]); + ObjClass *baseClassObj = AS_CLASS(args[1]); + + // Walk the superclass chain looking for the class. + do + { + if (baseClassObj == classObj) RETURN_BOOL(true); + + classObj = classObj->superclass; + } + while (classObj != NULL); + + RETURN_BOOL(false); +} + +DEF_PRIMITIVE(object_toString) +{ + Obj* obj = AS_OBJ(args[0]); + Value name = OBJ_VAL(obj->classObj->name); + RETURN_VAL(wrenStringFormat(vm, "instance of @", name)); +} + +DEF_PRIMITIVE(object_type) +{ + RETURN_OBJ(wrenGetClass(vm, args[0])); +} + +DEF_PRIMITIVE(range_from) +{ + RETURN_NUM(AS_RANGE(args[0])->from); +} + +DEF_PRIMITIVE(range_to) +{ + RETURN_NUM(AS_RANGE(args[0])->to); +} + +DEF_PRIMITIVE(range_min) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmin(range->from, range->to)); +} + +DEF_PRIMITIVE(range_max) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmax(range->from, range->to)); +} + +DEF_PRIMITIVE(range_isInclusive) +{ + RETURN_BOOL(AS_RANGE(args[0])->isInclusive); +} + +DEF_PRIMITIVE(range_iterate) +{ + ObjRange* range = AS_RANGE(args[0]); + + // Special case: empty range. + if (range->from == range->to && !range->isInclusive) RETURN_FALSE; + + // Start the iteration. + if (IS_NULL(args[1])) RETURN_NUM(range->from); + + if (!validateNum(vm, args[1], "Iterator")) return false; + + double iterator = AS_NUM(args[1]); + + // Iterate towards [to] from [from]. + if (range->from < range->to) + { + iterator++; + if (iterator > range->to) RETURN_FALSE; + } + else + { + iterator--; + if (iterator < range->to) RETURN_FALSE; + } + + if (!range->isInclusive && iterator == range->to) RETURN_FALSE; + + RETURN_NUM(iterator); +} + +DEF_PRIMITIVE(range_iteratorValue) +{ + // Assume the iterator is a number so that is the value of the range. + RETURN_VAL(args[1]); +} + +DEF_PRIMITIVE(range_toString) +{ + ObjRange* range = AS_RANGE(args[0]); + + Value from = wrenNumToString(vm, range->from); + wrenPushRoot(vm, AS_OBJ(from)); + + Value to = wrenNumToString(vm, range->to); + wrenPushRoot(vm, AS_OBJ(to)); + + Value result = wrenStringFormat(vm, "@$@", from, + range->isInclusive ? ".." : "...", to); + + wrenPopRoot(vm); + wrenPopRoot(vm); + RETURN_VAL(result); +} + +DEF_PRIMITIVE(string_fromCodePoint) +{ + if (!validateInt(vm, args[1], "Code point")) return false; + + int codePoint = (int)AS_NUM(args[1]); + if (codePoint < 0) + { + RETURN_ERROR("Code point cannot be negative."); + } + else if (codePoint > 0x10ffff) + { + RETURN_ERROR("Code point cannot be greater than 0x10ffff."); + } + + RETURN_VAL(wrenStringFromCodePoint(vm, codePoint)); +} + +DEF_PRIMITIVE(string_fromByte) +{ + if (!validateInt(vm, args[1], "Byte")) return false; + int byte = (int) AS_NUM(args[1]); + if (byte < 0) + { + RETURN_ERROR("Byte cannot be negative."); + } + else if (byte > 0xff) + { + RETURN_ERROR("Byte cannot be greater than 0xff."); + } + RETURN_VAL(wrenStringFromByte(vm, (uint8_t) byte)); +} + +DEF_PRIMITIVE(string_byteAt) +{ + ObjString* string = AS_STRING(args[0]); + + uint32_t index = validateIndex(vm, args[1], string->length, "Index"); + if (index == UINT32_MAX) return false; + + RETURN_NUM((uint8_t)string->value[index]); +} + +DEF_PRIMITIVE(string_byteCount) +{ + RETURN_NUM(AS_STRING(args[0])->length); +} + +DEF_PRIMITIVE(string_codePointAt) +{ + ObjString* string = AS_STRING(args[0]); + + uint32_t index = validateIndex(vm, args[1], string->length, "Index"); + if (index == UINT32_MAX) return false; + + // If we are in the middle of a UTF-8 sequence, indicate that. + const uint8_t* bytes = (uint8_t*)string->value; + if ((bytes[index] & 0xc0) == 0x80) RETURN_NUM(-1); + + // Decode the UTF-8 sequence. + RETURN_NUM(wrenUtf8Decode((uint8_t*)string->value + index, + string->length - index)); +} + +DEF_PRIMITIVE(string_contains) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + RETURN_BOOL(wrenStringFind(string, search, 0) != UINT32_MAX); +} + +DEF_PRIMITIVE(string_endsWith) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + // Edge case: If the search string is longer then return false right away. + if (search->length > string->length) RETURN_FALSE; + + RETURN_BOOL(memcmp(string->value + string->length - search->length, + search->value, search->length) == 0); +} + +DEF_PRIMITIVE(string_indexOf1) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + uint32_t index = wrenStringFind(string, search, 0); + RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); +} + +DEF_PRIMITIVE(string_indexOf2) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + uint32_t start = validateIndex(vm, args[2], string->length, "Start"); + if (start == UINT32_MAX) return false; + + uint32_t index = wrenStringFind(string, search, start); + RETURN_NUM(index == UINT32_MAX ? -1 : (int)index); +} + +DEF_PRIMITIVE(string_iterate) +{ + ObjString* string = AS_STRING(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) + { + if (string->length == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + uint32_t index = (uint32_t)AS_NUM(args[1]); + + // Advance to the beginning of the next UTF-8 sequence. + do + { + index++; + if (index >= string->length) RETURN_FALSE; + } while ((string->value[index] & 0xc0) == 0x80); + + RETURN_NUM(index); +} + +DEF_PRIMITIVE(string_iterateByte) +{ + ObjString* string = AS_STRING(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) + { + if (string->length == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + if (AS_NUM(args[1]) < 0) RETURN_FALSE; + uint32_t index = (uint32_t)AS_NUM(args[1]); + + // Advance to the next byte. + index++; + if (index >= string->length) RETURN_FALSE; + + RETURN_NUM(index); +} + +DEF_PRIMITIVE(string_iteratorValue) +{ + ObjString* string = AS_STRING(args[0]); + uint32_t index = validateIndex(vm, args[1], string->length, "Iterator"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); +} + +DEF_PRIMITIVE(string_startsWith) +{ + if (!validateString(vm, args[1], "Argument")) return false; + + ObjString* string = AS_STRING(args[0]); + ObjString* search = AS_STRING(args[1]); + + // Edge case: If the search string is longer then return false right away. + if (search->length > string->length) RETURN_FALSE; + + RETURN_BOOL(memcmp(string->value, search->value, search->length) == 0); +} + +DEF_PRIMITIVE(string_plus) +{ + if (!validateString(vm, args[1], "Right operand")) return false; + RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1])); +} + +DEF_PRIMITIVE(string_subscript) +{ + ObjString* string = AS_STRING(args[0]); + + if (IS_NUM(args[1])) + { + int index = validateIndex(vm, args[1], string->length, "Subscript"); + if (index == -1) return false; + + RETURN_VAL(wrenStringCodePointAt(vm, string, index)); + } + + if (!IS_RANGE(args[1])) + { + RETURN_ERROR("Subscript must be a number or a range."); + } + + int step; + uint32_t count = string->length; + int start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); + if (start == -1) return false; + + RETURN_VAL(wrenNewStringFromRange(vm, string, start, count, step)); +} + +DEF_PRIMITIVE(string_toString) +{ + RETURN_VAL(args[0]); +} + +DEF_PRIMITIVE(system_clock) +{ + RETURN_NUM((double)clock() / CLOCKS_PER_SEC); +} + +DEF_PRIMITIVE(system_gc) +{ + wrenCollectGarbage(vm); + RETURN_NULL; +} + +DEF_PRIMITIVE(system_writeString) +{ + if (vm->config.writeFn != NULL) + { + vm->config.writeFn(vm, AS_CSTRING(args[1])); + } + + RETURN_VAL(args[1]); +} + +// Creates either the Object or Class class in the core module with [name]. +static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) +{ + ObjString* nameString = AS_STRING(wrenNewString(vm, name)); + wrenPushRoot(vm, (Obj*)nameString); + + ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); + + wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); + + wrenPopRoot(vm); + return classObj; +} + +void wrenInitializeCore(WrenVM* vm) +{ + ObjModule* coreModule = wrenNewModule(vm, NULL); + wrenPushRoot(vm, (Obj*)coreModule); + + // The core module's key is null in the module map. + wrenMapSet(vm, vm->modules, NULL_VAL, OBJ_VAL(coreModule)); + wrenPopRoot(vm); // coreModule. + + // Define the root Object class. This has to be done a little specially + // because it has no superclass. + vm->objectClass = defineClass(vm, coreModule, "Object"); + PRIMITIVE(vm->objectClass, "!", object_not); + PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); + PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); + PRIMITIVE(vm->objectClass, "is(_)", object_is); + PRIMITIVE(vm->objectClass, "toString", object_toString); + PRIMITIVE(vm->objectClass, "type", object_type); + + // Now we can define Class, which is a subclass of Object. + vm->classClass = defineClass(vm, coreModule, "Class"); + wrenBindSuperclass(vm, vm->classClass, vm->objectClass); + PRIMITIVE(vm->classClass, "name", class_name); + PRIMITIVE(vm->classClass, "supertype", class_supertype); + PRIMITIVE(vm->classClass, "toString", class_toString); + PRIMITIVE(vm->classClass, "attributes", class_attributes); + + // Finally, we can define Object's metaclass which is a subclass of Class. + ObjClass* objectMetaclass = defineClass(vm, coreModule, "Object metaclass"); + + // Wire up the metaclass relationships now that all three classes are built. + vm->objectClass->obj.classObj = objectMetaclass; + objectMetaclass->obj.classObj = vm->classClass; + vm->classClass->obj.classObj = vm->classClass; + + // Do this after wiring up the metaclasses so objectMetaclass doesn't get + // collected. + wrenBindSuperclass(vm, objectMetaclass, vm->classClass); + + PRIMITIVE(objectMetaclass, "same(_,_)", object_same); + + // The core class diagram ends up looking like this, where single lines point + // to a class's superclass, and double lines point to its metaclass: + // + // .------------------------------------. .====. + // | .---------------. | # # + // v | v | v # + // .---------. .-------------------. .-------. # + // | Object |==>| Object metaclass |==>| Class |==" + // '---------' '-------------------' '-------' + // ^ ^ ^ ^ ^ + // | .--------------' # | # + // | | # | # + // .---------. .-------------------. # | # -. + // | Base |==>| Base metaclass |======" | # | + // '---------' '-------------------' | # | + // ^ | # | + // | .------------------' # | Example classes + // | | # | + // .---------. .-------------------. # | + // | Derived |==>| Derived metaclass |==========" | + // '---------' '-------------------' -' + + // The rest of the classes can now be defined normally. + wrenInterpret(vm, NULL, coreModuleSource); + + vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool")); + PRIMITIVE(vm->boolClass, "toString", bool_toString); + PRIMITIVE(vm->boolClass, "!", bool_not); + + vm->fiberClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fiber")); + PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); + PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); + PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); + PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); + PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); + PRIMITIVE(vm->fiberClass, "call()", fiber_call); + PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); + PRIMITIVE(vm->fiberClass, "error", fiber_error); + PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); + PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); + PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); + PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError); + PRIMITIVE(vm->fiberClass, "try()", fiber_try); + PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1); + + vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); + PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new); + + PRIMITIVE(vm->fnClass, "arity", fn_arity); + + FUNCTION_CALL(vm->fnClass, "call()", fn_call0); + FUNCTION_CALL(vm->fnClass, "call(_)", fn_call1); + FUNCTION_CALL(vm->fnClass, "call(_,_)", fn_call2); + FUNCTION_CALL(vm->fnClass, "call(_,_,_)", fn_call3); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_)", fn_call4); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_)", fn_call5); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15); + FUNCTION_CALL(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16); + + PRIMITIVE(vm->fnClass, "toString", fn_toString); + + vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null")); + PRIMITIVE(vm->nullClass, "!", null_not); + PRIMITIVE(vm->nullClass, "toString", null_toString); + + vm->numClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Num")); + PRIMITIVE(vm->numClass->obj.classObj, "fromString(_)", num_fromString); + PRIMITIVE(vm->numClass->obj.classObj, "infinity", num_infinity); + PRIMITIVE(vm->numClass->obj.classObj, "nan", num_nan); + PRIMITIVE(vm->numClass->obj.classObj, "pi", num_pi); + PRIMITIVE(vm->numClass->obj.classObj, "tau", num_tau); + PRIMITIVE(vm->numClass->obj.classObj, "largest", num_largest); + PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest); + PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger); + PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger); + PRIMITIVE(vm->numClass, "-(_)", num_minus); + PRIMITIVE(vm->numClass, "+(_)", num_plus); + PRIMITIVE(vm->numClass, "*(_)", num_multiply); + PRIMITIVE(vm->numClass, "/(_)", num_divide); + PRIMITIVE(vm->numClass, "<(_)", num_lt); + PRIMITIVE(vm->numClass, ">(_)", num_gt); + PRIMITIVE(vm->numClass, "<=(_)", num_lte); + PRIMITIVE(vm->numClass, ">=(_)", num_gte); + PRIMITIVE(vm->numClass, "&(_)", num_bitwiseAnd); + PRIMITIVE(vm->numClass, "|(_)", num_bitwiseOr); + PRIMITIVE(vm->numClass, "^(_)", num_bitwiseXor); + PRIMITIVE(vm->numClass, "<<(_)", num_bitwiseLeftShift); + PRIMITIVE(vm->numClass, ">>(_)", num_bitwiseRightShift); + PRIMITIVE(vm->numClass, "abs", num_abs); + PRIMITIVE(vm->numClass, "acos", num_acos); + PRIMITIVE(vm->numClass, "asin", num_asin); + PRIMITIVE(vm->numClass, "atan", num_atan); + PRIMITIVE(vm->numClass, "cbrt", num_cbrt); + PRIMITIVE(vm->numClass, "ceil", num_ceil); + PRIMITIVE(vm->numClass, "cos", num_cos); + PRIMITIVE(vm->numClass, "floor", num_floor); + PRIMITIVE(vm->numClass, "-", num_negate); + PRIMITIVE(vm->numClass, "round", num_round); + PRIMITIVE(vm->numClass, "min(_)", num_min); + PRIMITIVE(vm->numClass, "max(_)", num_max); + PRIMITIVE(vm->numClass, "clamp(_,_)", num_clamp); + PRIMITIVE(vm->numClass, "sin", num_sin); + PRIMITIVE(vm->numClass, "sqrt", num_sqrt); + PRIMITIVE(vm->numClass, "tan", num_tan); + PRIMITIVE(vm->numClass, "log", num_log); + PRIMITIVE(vm->numClass, "log2", num_log2); + PRIMITIVE(vm->numClass, "exp", num_exp); + PRIMITIVE(vm->numClass, "%(_)", num_mod); + PRIMITIVE(vm->numClass, "~", num_bitwiseNot); + PRIMITIVE(vm->numClass, "..(_)", num_dotDot); + PRIMITIVE(vm->numClass, "...(_)", num_dotDotDot); + PRIMITIVE(vm->numClass, "atan(_)", num_atan2); + PRIMITIVE(vm->numClass, "pow(_)", num_pow); + PRIMITIVE(vm->numClass, "fraction", num_fraction); + PRIMITIVE(vm->numClass, "isInfinity", num_isInfinity); + PRIMITIVE(vm->numClass, "isInteger", num_isInteger); + PRIMITIVE(vm->numClass, "isNan", num_isNan); + PRIMITIVE(vm->numClass, "sign", num_sign); + PRIMITIVE(vm->numClass, "toString", num_toString); + PRIMITIVE(vm->numClass, "truncate", num_truncate); + + // These are defined just so that 0 and -0 are equal, which is specified by + // IEEE 754 even though they have different bit representations. + PRIMITIVE(vm->numClass, "==(_)", num_eqeq); + PRIMITIVE(vm->numClass, "!=(_)", num_bangeq); + + vm->stringClass = AS_CLASS(wrenFindVariable(vm, coreModule, "String")); + PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint); + PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte); + PRIMITIVE(vm->stringClass, "+(_)", string_plus); + PRIMITIVE(vm->stringClass, "[_]", string_subscript); + PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt); + PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount); + PRIMITIVE(vm->stringClass, "codePointAt_(_)", string_codePointAt); + PRIMITIVE(vm->stringClass, "contains(_)", string_contains); + PRIMITIVE(vm->stringClass, "endsWith(_)", string_endsWith); + PRIMITIVE(vm->stringClass, "indexOf(_)", string_indexOf1); + PRIMITIVE(vm->stringClass, "indexOf(_,_)", string_indexOf2); + PRIMITIVE(vm->stringClass, "iterate(_)", string_iterate); + PRIMITIVE(vm->stringClass, "iterateByte_(_)", string_iterateByte); + PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue); + PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith); + PRIMITIVE(vm->stringClass, "toString", string_toString); + + vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List")); + PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled); + PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new); + PRIMITIVE(vm->listClass, "[_]", list_subscript); + PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter); + PRIMITIVE(vm->listClass, "add(_)", list_add); + PRIMITIVE(vm->listClass, "addCore_(_)", list_addCore); + PRIMITIVE(vm->listClass, "clear()", list_clear); + PRIMITIVE(vm->listClass, "count", list_count); + PRIMITIVE(vm->listClass, "insert(_,_)", list_insert); + PRIMITIVE(vm->listClass, "iterate(_)", list_iterate); + PRIMITIVE(vm->listClass, "iteratorValue(_)", list_iteratorValue); + PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt); + PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); + PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); + PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); + + vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); + PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); + PRIMITIVE(vm->mapClass, "[_]", map_subscript); + PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter); + PRIMITIVE(vm->mapClass, "addCore_(_,_)", map_addCore); + PRIMITIVE(vm->mapClass, "clear()", map_clear); + PRIMITIVE(vm->mapClass, "containsKey(_)", map_containsKey); + PRIMITIVE(vm->mapClass, "count", map_count); + PRIMITIVE(vm->mapClass, "remove(_)", map_remove); + PRIMITIVE(vm->mapClass, "iterate(_)", map_iterate); + PRIMITIVE(vm->mapClass, "keyIteratorValue_(_)", map_keyIteratorValue); + PRIMITIVE(vm->mapClass, "valueIteratorValue_(_)", map_valueIteratorValue); + + vm->rangeClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Range")); + PRIMITIVE(vm->rangeClass, "from", range_from); + PRIMITIVE(vm->rangeClass, "to", range_to); + PRIMITIVE(vm->rangeClass, "min", range_min); + PRIMITIVE(vm->rangeClass, "max", range_max); + PRIMITIVE(vm->rangeClass, "isInclusive", range_isInclusive); + PRIMITIVE(vm->rangeClass, "iterate(_)", range_iterate); + PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); + PRIMITIVE(vm->rangeClass, "toString", range_toString); + + ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); + PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); + PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); + PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString); + + // While bootstrapping the core types and running the core module, a number + // of string objects have been created, many of which were instantiated + // before stringClass was stored in the VM. Some of them *must* be created + // first -- the ObjClass for string itself has a reference to the ObjString + // for its name. + // + // These all currently have a NULL classObj pointer, so go back and assign + // them now that the string class is known. + for (Obj* obj = vm->first; obj != NULL; obj = obj->next) + { + if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass; + } +} +// End file "wren_core.c" +// Begin file "wren_value.c" +#include +#include +#include +#include + + +#if WREN_DEBUG_TRACE_MEMORY +#endif + +// TODO: Tune these. +// The initial (and minimum) capacity of a non-empty list or map object. +#define MIN_CAPACITY 16 + +// The rate at which a collection's capacity grows when the size exceeds the +// current capacity. The new capacity will be determined by *multiplying* the +// old capacity by this. Growing geometrically is necessary to ensure that +// adding to a collection has O(1) amortized complexity. +#define GROW_FACTOR 2 + +// The maximum percentage of map entries that can be filled before the map is +// grown. A lower load takes more memory but reduces collisions which makes +// lookup faster. +#define MAP_LOAD_PERCENT 75 + +// The number of call frames initially allocated when a fiber is created. Making +// this smaller makes fibers use less memory (at first) but spends more time +// reallocating when the call stack grows. +#define INITIAL_CALL_FRAMES 4 + +DEFINE_BUFFER(Value, Value); +DEFINE_BUFFER(Method, Method); + +static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) +{ + obj->type = type; + obj->isDark = false; + obj->classObj = classObj; + obj->next = vm->first; + vm->first = obj; +} + +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) +{ + ObjClass* classObj = ALLOCATE(vm, ObjClass); + initObj(vm, &classObj->obj, OBJ_CLASS, NULL); + classObj->superclass = NULL; + classObj->numFields = numFields; + classObj->name = name; + classObj->attributes = NULL_VAL; + + wrenPushRoot(vm, (Obj*)classObj); + wrenMethodBufferInit(&classObj->methods); + wrenPopRoot(vm); + + return classObj; +} + +void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) +{ + ASSERT(superclass != NULL, "Must have superclass."); + + subclass->superclass = superclass; + + // Include the superclass in the total number of fields. + if (subclass->numFields != -1) + { + subclass->numFields += superclass->numFields; + } + else + { + ASSERT(superclass->numFields == 0, + "A foreign class cannot inherit from a class with fields."); + } + + // Inherit methods from its superclass. + for (int i = 0; i < superclass->methods.count; i++) + { + wrenBindMethod(vm, subclass, i, superclass->methods.data[i]); + } +} + +ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, + ObjString* name) +{ + // Create the metaclass. + Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); + wrenPushRoot(vm, AS_OBJ(metaclassName)); + + ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); + metaclass->obj.classObj = vm->classClass; + + wrenPopRoot(vm); + + // Make sure the metaclass isn't collected when we allocate the class. + wrenPushRoot(vm, (Obj*)metaclass); + + // Metaclasses always inherit Class and do not parallel the non-metaclass + // hierarchy. + wrenBindSuperclass(vm, metaclass, vm->classClass); + + ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); + + // Make sure the class isn't collected while the inherited methods are being + // bound. + wrenPushRoot(vm, (Obj*)classObj); + + classObj->obj.classObj = metaclass; + wrenBindSuperclass(vm, classObj, superclass); + + wrenPopRoot(vm); + wrenPopRoot(vm); + + return classObj; +} + +void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method) +{ + // Make sure the buffer is big enough to contain the symbol's index. + if (symbol >= classObj->methods.count) + { + Method noMethod; + noMethod.type = METHOD_NONE; + wrenMethodBufferFill(vm, &classObj->methods, noMethod, + symbol - classObj->methods.count + 1); + } + + classObj->methods.data[symbol] = method; +} + +ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn) +{ + ObjClosure* closure = ALLOCATE_FLEX(vm, ObjClosure, + ObjUpvalue*, fn->numUpvalues); + initObj(vm, &closure->obj, OBJ_CLOSURE, vm->fnClass); + + closure->fn = fn; + + // Clear the upvalue array. We need to do this in case a GC is triggered + // after the closure is created but before the upvalue array is populated. + for (int i = 0; i < fn->numUpvalues; i++) closure->upvalues[i] = NULL; + + return closure; +} + +ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) +{ + // Allocate the arrays before the fiber in case it triggers a GC. + CallFrame* frames = ALLOCATE_ARRAY(vm, CallFrame, INITIAL_CALL_FRAMES); + + // Add one slot for the unused implicit receiver slot that the compiler + // assumes all functions have. + int stackCapacity = closure == NULL + ? 1 + : wrenPowerOf2Ceil(closure->fn->maxSlots + 1); + Value* stack = ALLOCATE_ARRAY(vm, Value, stackCapacity); + + ObjFiber* fiber = ALLOCATE(vm, ObjFiber); + initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); + + fiber->stack = stack; + fiber->stackTop = fiber->stack; + fiber->stackCapacity = stackCapacity; + + fiber->frames = frames; + fiber->frameCapacity = INITIAL_CALL_FRAMES; + fiber->numFrames = 0; + + fiber->openUpvalues = NULL; + fiber->caller = NULL; + fiber->error = NULL_VAL; + fiber->state = FIBER_OTHER; + + if (closure != NULL) + { + // Initialize the first call frame. + wrenAppendCallFrame(vm, fiber, closure, fiber->stack); + + // The first slot always holds the closure. + fiber->stackTop[0] = OBJ_VAL(closure); + fiber->stackTop++; + } + + return fiber; +} + +void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) +{ + if (fiber->stackCapacity >= needed) return; + + int capacity = wrenPowerOf2Ceil(needed); + + Value* oldStack = fiber->stack; + fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, + sizeof(Value) * fiber->stackCapacity, + sizeof(Value) * capacity); + fiber->stackCapacity = capacity; + + // If the reallocation moves the stack, then we need to recalculate every + // pointer that points into the old stack to into the same relative distance + // in the new stack. We have to be a little careful about how these are + // calculated because pointer subtraction is only well-defined within a + // single array, hence the slightly redundant-looking arithmetic below. + if (fiber->stack != oldStack) + { + // Top of the stack. + if (vm->apiStack >= oldStack && vm->apiStack <= fiber->stackTop) + { + vm->apiStack = fiber->stack + (vm->apiStack - oldStack); + } + + // Stack pointer for each call frame. + for (int i = 0; i < fiber->numFrames; i++) + { + CallFrame* frame = &fiber->frames[i]; + frame->stackStart = fiber->stack + (frame->stackStart - oldStack); + } + + // Open upvalues. + for (ObjUpvalue* upvalue = fiber->openUpvalues; + upvalue != NULL; + upvalue = upvalue->next) + { + upvalue->value = fiber->stack + (upvalue->value - oldStack); + } + + fiber->stackTop = fiber->stack + (fiber->stackTop - oldStack); + } +} + +ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) +{ + ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); + initObj(vm, &object->obj, OBJ_FOREIGN, classObj); + + // Zero out the bytes. + memset(object->data, 0, size); + return object; +} + +ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) +{ + FnDebug* debug = ALLOCATE(vm, FnDebug); + debug->name = NULL; + wrenIntBufferInit(&debug->sourceLines); + + ObjFn* fn = ALLOCATE(vm, ObjFn); + initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); + + wrenValueBufferInit(&fn->constants); + wrenByteBufferInit(&fn->code); + fn->module = module; + fn->maxSlots = maxSlots; + fn->numUpvalues = 0; + fn->arity = 0; + fn->debug = debug; + + return fn; +} + +void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) +{ + fn->debug->name = ALLOCATE_ARRAY(vm, char, length + 1); + memcpy(fn->debug->name, name, length); + fn->debug->name[length] = '\0'; +} + +Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) +{ + ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, + Value, classObj->numFields); + initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); + + // Initialize fields to null. + for (int i = 0; i < classObj->numFields; i++) + { + instance->fields[i] = NULL_VAL; + } + + return OBJ_VAL(instance); +} + +ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) +{ + // Allocate this before the list object in case it triggers a GC which would + // free the list. + Value* elements = NULL; + if (numElements > 0) + { + elements = ALLOCATE_ARRAY(vm, Value, numElements); + } + + ObjList* list = ALLOCATE(vm, ObjList); + initObj(vm, &list->obj, OBJ_LIST, vm->listClass); + list->elements.capacity = numElements; + list->elements.count = numElements; + list->elements.data = elements; + return list; +} + +void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index) +{ + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + // Add a slot at the end of the list. + wrenValueBufferWrite(vm, &list->elements, NULL_VAL); + + if (IS_OBJ(value)) wrenPopRoot(vm); + + // Shift the existing elements down. + for (uint32_t i = list->elements.count - 1; i > index; i--) + { + list->elements.data[i] = list->elements.data[i - 1]; + } + + // Store the new element. + list->elements.data[index] = value; +} + +int wrenListIndexOf(WrenVM* vm, ObjList* list, Value value) +{ + int count = list->elements.count; + for (int i = 0; i < count; i++) + { + Value item = list->elements.data[i]; + if(wrenValuesEqual(item, value)) { + return i; + } + } + return -1; +} + +Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index) +{ + Value removed = list->elements.data[index]; + + if (IS_OBJ(removed)) wrenPushRoot(vm, AS_OBJ(removed)); + + // Shift items up. + for (int i = index; i < list->elements.count - 1; i++) + { + list->elements.data[i] = list->elements.data[i + 1]; + } + + // If we have too much excess capacity, shrink it. + if (list->elements.capacity / GROW_FACTOR >= list->elements.count) + { + list->elements.data = (Value*)wrenReallocate(vm, list->elements.data, + sizeof(Value) * list->elements.capacity, + sizeof(Value) * (list->elements.capacity / GROW_FACTOR)); + list->elements.capacity /= GROW_FACTOR; + } + + if (IS_OBJ(removed)) wrenPopRoot(vm); + + list->elements.count--; + return removed; +} + +ObjMap* wrenNewMap(WrenVM* vm) +{ + ObjMap* map = ALLOCATE(vm, ObjMap); + initObj(vm, &map->obj, OBJ_MAP, vm->mapClass); + map->capacity = 0; + map->count = 0; + map->entries = NULL; + return map; +} + +static inline uint32_t hashBits(uint64_t hash) +{ + // From v8's ComputeLongHash() which in turn cites: + // Thomas Wang, Integer Hash Functions. + // http://www.concentric.net/~Ttwang/tech/inthash.htm + hash = ~hash + (hash << 18); // hash = (hash << 18) - hash - 1; + hash = hash ^ (hash >> 31); + hash = hash * 21; // hash = (hash + (hash << 2)) + (hash << 4); + hash = hash ^ (hash >> 11); + hash = hash + (hash << 6); + hash = hash ^ (hash >> 22); + return (uint32_t)(hash & 0x3fffffff); +} + +// Generates a hash code for [num]. +static inline uint32_t hashNumber(double num) +{ + // Hash the raw bits of the value. + return hashBits(wrenDoubleToBits(num)); +} + +// Generates a hash code for [object]. +static uint32_t hashObject(Obj* object) +{ + switch (object->type) + { + case OBJ_CLASS: + // Classes just use their name. + return hashObject((Obj*)((ObjClass*)object)->name); + + // Allow bare (non-closure) functions so that we can use a map to find + // existing constants in a function's constant table. This is only used + // internally. Since user code never sees a non-closure function, they + // cannot use them as map keys. + case OBJ_FN: + { + ObjFn* fn = (ObjFn*)object; + return hashNumber(fn->arity) ^ hashNumber(fn->code.count); + } + + case OBJ_RANGE: + { + ObjRange* range = (ObjRange*)object; + return hashNumber(range->from) ^ hashNumber(range->to); + } + + case OBJ_STRING: + return ((ObjString*)object)->hash; + + default: + ASSERT(false, "Only immutable objects can be hashed."); + return 0; + } +} + +// Generates a hash code for [value], which must be one of the built-in +// immutable types: null, bool, class, num, range, or string. +static uint32_t hashValue(Value value) +{ + // TODO: We'll probably want to randomize this at some point. + +#if WREN_NAN_TAGGING + if (IS_OBJ(value)) return hashObject(AS_OBJ(value)); + + // Hash the raw bits of the unboxed value. + return hashBits(value); +#else + switch (value.type) + { + case VAL_FALSE: return 0; + case VAL_NULL: return 1; + case VAL_NUM: return hashNumber(AS_NUM(value)); + case VAL_TRUE: return 2; + case VAL_OBJ: return hashObject(AS_OBJ(value)); + default: UNREACHABLE(); + } + + return 0; +#endif +} + +// Looks for an entry with [key] in an array of [capacity] [entries]. +// +// If found, sets [result] to point to it and returns `true`. Otherwise, +// returns `false` and points [result] to the entry where the key/value pair +// should be inserted. +static bool findEntry(MapEntry* entries, uint32_t capacity, Value key, + MapEntry** result) +{ + // If there is no entry array (an empty map), we definitely won't find it. + if (capacity == 0) return false; + + // Figure out where to insert it in the table. Use open addressing and + // basic linear probing. + uint32_t startIndex = hashValue(key) % capacity; + uint32_t index = startIndex; + + // If we pass a tombstone and don't end up finding the key, its entry will + // be re-used for the insert. + MapEntry* tombstone = NULL; + + // Walk the probe sequence until we've tried every slot. + do + { + MapEntry* entry = &entries[index]; + + if (IS_UNDEFINED(entry->key)) + { + // If we found an empty slot, the key is not in the table. If we found a + // slot that contains a deleted key, we have to keep looking. + if (IS_FALSE(entry->value)) + { + // We found an empty slot, so we've reached the end of the probe + // sequence without finding the key. If we passed a tombstone, then + // that's where we should insert the item, otherwise, put it here at + // the end of the sequence. + *result = tombstone != NULL ? tombstone : entry; + return false; + } + else + { + // We found a tombstone. We need to keep looking in case the key is + // after it, but we'll use this entry as the insertion point if the + // key ends up not being found. + if (tombstone == NULL) tombstone = entry; + } + } + else if (wrenValuesEqual(entry->key, key)) + { + // We found the key. + *result = entry; + return true; + } + + // Try the next slot. + index = (index + 1) % capacity; + } + while (index != startIndex); + + // If we get here, the table is full of tombstones. Return the first one we + // found. + ASSERT(tombstone != NULL, "Map should have tombstones or empty entries."); + *result = tombstone; + return false; +} + +// Inserts [key] and [value] in the array of [entries] with the given +// [capacity]. +// +// Returns `true` if this is the first time [key] was added to the map. +static bool insertEntry(MapEntry* entries, uint32_t capacity, + Value key, Value value) +{ + ASSERT(entries != NULL, "Should ensure capacity before inserting."); + + MapEntry* entry; + if (findEntry(entries, capacity, key, &entry)) + { + // Already present, so just replace the value. + entry->value = value; + return false; + } + else + { + entry->key = key; + entry->value = value; + return true; + } +} + +// Updates [map]'s entry array to [capacity]. +static void resizeMap(WrenVM* vm, ObjMap* map, uint32_t capacity) +{ + // Create the new empty hash table. + MapEntry* entries = ALLOCATE_ARRAY(vm, MapEntry, capacity); + for (uint32_t i = 0; i < capacity; i++) + { + entries[i].key = UNDEFINED_VAL; + entries[i].value = FALSE_VAL; + } + + // Re-add the existing entries. + if (map->capacity > 0) + { + for (uint32_t i = 0; i < map->capacity; i++) + { + MapEntry* entry = &map->entries[i]; + + // Don't copy empty entries or tombstones. + if (IS_UNDEFINED(entry->key)) continue; + + insertEntry(entries, capacity, entry->key, entry->value); + } + } + + // Replace the array. + DEALLOCATE(vm, map->entries); + map->entries = entries; + map->capacity = capacity; +} + +Value wrenMapGet(ObjMap* map, Value key) +{ + MapEntry* entry; + if (findEntry(map->entries, map->capacity, key, &entry)) return entry->value; + + return UNDEFINED_VAL; +} + +void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value) +{ + // If the map is getting too full, make room first. + if (map->count + 1 > map->capacity * MAP_LOAD_PERCENT / 100) + { + // Figure out the new hash table size. + uint32_t capacity = map->capacity * GROW_FACTOR; + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; + + resizeMap(vm, map, capacity); + } + + if (insertEntry(map->entries, map->capacity, key, value)) + { + // A new key was added. + map->count++; + } +} + +void wrenMapClear(WrenVM* vm, ObjMap* map) +{ + DEALLOCATE(vm, map->entries); + map->entries = NULL; + map->capacity = 0; + map->count = 0; +} + +Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key) +{ + MapEntry* entry; + if (!findEntry(map->entries, map->capacity, key, &entry)) return NULL_VAL; + + // Remove the entry from the map. Set this value to true, which marks it as a + // deleted slot. When searching for a key, we will stop on empty slots, but + // continue past deleted slots. + Value value = entry->value; + entry->key = UNDEFINED_VAL; + entry->value = TRUE_VAL; + + if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + map->count--; + + if (map->count == 0) + { + // Removed the last item, so free the array. + wrenMapClear(vm, map); + } + else if (map->capacity > MIN_CAPACITY && + map->count < map->capacity / GROW_FACTOR * MAP_LOAD_PERCENT / 100) + { + uint32_t capacity = map->capacity / GROW_FACTOR; + if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; + + // The map is getting empty, so shrink the entry array back down. + // TODO: Should we do this less aggressively than we grow? + resizeMap(vm, map, capacity); + } + + if (IS_OBJ(value)) wrenPopRoot(vm); + return value; +} + +ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) +{ + ObjModule* module = ALLOCATE(vm, ObjModule); + + // Modules are never used as first-class objects, so don't need a class. + initObj(vm, (Obj*)module, OBJ_MODULE, NULL); + + wrenPushRoot(vm, (Obj*)module); + + wrenSymbolTableInit(&module->variableNames); + wrenValueBufferInit(&module->variables); + + module->name = name; + + wrenPopRoot(vm); + return module; +} + +Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive) +{ + ObjRange* range = ALLOCATE(vm, ObjRange); + initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass); + range->from = from; + range->to = to; + range->isInclusive = isInclusive; + + return OBJ_VAL(range); +} + +// Creates a new string object with a null-terminated buffer large enough to +// hold a string of [length] but does not fill in the bytes. +// +// The caller is expected to fill in the buffer and then calculate the string's +// hash. +static ObjString* allocateString(WrenVM* vm, size_t length) +{ + ObjString* string = ALLOCATE_FLEX(vm, ObjString, char, length + 1); + initObj(vm, &string->obj, OBJ_STRING, vm->stringClass); + string->length = (int)length; + string->value[length] = '\0'; + + return string; +} + +// Calculates and stores the hash code for [string]. +static void hashString(ObjString* string) +{ + // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ + uint32_t hash = 2166136261u; + + // This is O(n) on the length of the string, but we only call this when a new + // string is created. Since the creation is also O(n) (to copy/initialize all + // the bytes), we allow this here. + for (uint32_t i = 0; i < string->length; i++) + { + hash ^= string->value[i]; + hash *= 16777619; + } + + string->hash = hash; +} + +Value wrenNewString(WrenVM* vm, const char* text) +{ + return wrenNewStringLength(vm, text, strlen(text)); +} + +Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length) +{ + // Allow NULL if the string is empty since byte buffers don't allocate any + // characters for a zero-length string. + ASSERT(length == 0 || text != NULL, "Unexpected NULL string."); + + ObjString* string = allocateString(vm, length); + + // Copy the string (if given one). + if (length > 0 && text != NULL) memcpy(string->value, text, length); + + hashString(string); + return OBJ_VAL(string); +} + + +Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, + uint32_t count, int step) +{ + uint8_t* from = (uint8_t*)source->value; + int length = 0; + for (uint32_t i = 0; i < count; i++) + { + length += wrenUtf8DecodeNumBytes(from[start + i * step]); + } + + ObjString* result = allocateString(vm, length); + result->value[length] = '\0'; + + uint8_t* to = (uint8_t*)result->value; + for (uint32_t i = 0; i < count; i++) + { + int index = start + i * step; + int codePoint = wrenUtf8Decode(from + index, source->length - index); + + if (codePoint != -1) + { + to += wrenUtf8Encode(codePoint, to); + } + } + + hashString(result); + return OBJ_VAL(result); +} + +Value wrenNumToString(WrenVM* vm, double value) +{ + // Edge case: If the value is NaN or infinity, different versions of libc + // produce different outputs (some will format it signed and some won't). To + // get reliable output, handle it ourselves. + if (isnan(value)) return CONST_STRING(vm, "nan"); + if (isinf(value)) + { + if (value > 0.0) + { + return CONST_STRING(vm, "infinity"); + } + else + { + return CONST_STRING(vm, "-infinity"); + } + } + + // This is large enough to hold any double converted to a string using + // "%.14g". Example: + // + // -1.12345678901234e-1022 + // + // So we have: + // + // + 1 char for sign + // + 1 char for digit + // + 1 char for "." + // + 14 chars for decimal digits + // + 1 char for "e" + // + 1 char for "-" or "+" + // + 4 chars for exponent + // + 1 char for "\0" + // = 24 + char buffer[24]; + int length = sprintf(buffer, "%.14g", value); + return wrenNewStringLength(vm, buffer, length); +} + +Value wrenStringFromCodePoint(WrenVM* vm, int value) +{ + int length = wrenUtf8EncodeNumBytes(value); + ASSERT(length != 0, "Value out of range."); + + ObjString* string = allocateString(vm, length); + + wrenUtf8Encode(value, (uint8_t*)string->value); + hashString(string); + + return OBJ_VAL(string); +} + +Value wrenStringFromByte(WrenVM *vm, uint8_t value) +{ + int length = 1; + ObjString* string = allocateString(vm, length); + string->value[0] = value; + hashString(string); + return OBJ_VAL(string); +} + +Value wrenStringFormat(WrenVM* vm, const char* format, ...) +{ + va_list argList; + + // Calculate the length of the result string. Do this up front so we can + // create the final string with a single allocation. + va_start(argList, format); + size_t totalLength = 0; + for (const char* c = format; *c != '\0'; c++) + { + switch (*c) + { + case '$': + totalLength += strlen(va_arg(argList, const char*)); + break; + + case '@': + totalLength += AS_STRING(va_arg(argList, Value))->length; + break; + + default: + // Any other character is interpreted literally. + totalLength++; + } + } + va_end(argList); + + // Concatenate the string. + ObjString* result = allocateString(vm, totalLength); + + va_start(argList, format); + char* start = result->value; + for (const char* c = format; *c != '\0'; c++) + { + switch (*c) + { + case '$': + { + const char* string = va_arg(argList, const char*); + size_t length = strlen(string); + memcpy(start, string, length); + start += length; + break; + } + + case '@': + { + ObjString* string = AS_STRING(va_arg(argList, Value)); + memcpy(start, string->value, string->length); + start += string->length; + break; + } + + default: + // Any other character is interpreted literally. + *start++ = *c; + } + } + va_end(argList); + + hashString(result); + + return OBJ_VAL(result); +} + +Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index) +{ + ASSERT(index < string->length, "Index out of bounds."); + + int codePoint = wrenUtf8Decode((uint8_t*)string->value + index, + string->length - index); + if (codePoint == -1) + { + // If it isn't a valid UTF-8 sequence, treat it as a single raw byte. + char bytes[2]; + bytes[0] = string->value[index]; + bytes[1] = '\0'; + return wrenNewStringLength(vm, bytes, 1); + } + + return wrenStringFromCodePoint(vm, codePoint); +} + +// Uses the Boyer-Moore-Horspool string matching algorithm. +uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) +{ + // Edge case: An empty needle is always found. + if (needle->length == 0) return start; + + // If the needle goes past the haystack it won't be found. + if (start + needle->length > haystack->length) return UINT32_MAX; + + // If the startIndex is too far it also won't be found. + if (start >= haystack->length) return UINT32_MAX; + + // Pre-calculate the shift table. For each character (8-bit value), we + // determine how far the search window can be advanced if that character is + // the last character in the haystack where we are searching for the needle + // and the needle doesn't match there. + uint32_t shift[UINT8_MAX]; + uint32_t needleEnd = needle->length - 1; + + // By default, we assume the character is not the needle at all. In that case + // case, if a match fails on that character, we can advance one whole needle + // width since. + for (uint32_t index = 0; index < UINT8_MAX; index++) + { + shift[index] = needle->length; + } + + // Then, for every character in the needle, determine how far it is from the + // end. If a match fails on that character, we can advance the window such + // that it the last character in it lines up with the last place we could + // find it in the needle. + for (uint32_t index = 0; index < needleEnd; index++) + { + char c = needle->value[index]; + shift[(uint8_t)c] = needleEnd - index; + } + + // Slide the needle across the haystack, looking for the first match or + // stopping if the needle goes off the end. + char lastChar = needle->value[needleEnd]; + uint32_t range = haystack->length - needle->length; + + for (uint32_t index = start; index <= range; ) + { + // Compare the last character in the haystack's window to the last character + // in the needle. If it matches, see if the whole needle matches. + char c = haystack->value[index + needleEnd]; + if (lastChar == c && + memcmp(haystack->value + index, needle->value, needleEnd) == 0) + { + // Found a match. + return index; + } + + // Otherwise, slide the needle forward. + index += shift[(uint8_t)c]; + } + + // Not found. + return UINT32_MAX; +} + +ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) +{ + ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); + + // Upvalues are never used as first-class objects, so don't need a class. + initObj(vm, &upvalue->obj, OBJ_UPVALUE, NULL); + + upvalue->value = value; + upvalue->closed = NULL_VAL; + upvalue->next = NULL; + return upvalue; +} + +void wrenGrayObj(WrenVM* vm, Obj* obj) +{ + if (obj == NULL) return; + + // Stop if the object is already darkened so we don't get stuck in a cycle. + if (obj->isDark) return; + + // It's been reached. + obj->isDark = true; + + // Add it to the gray list so it can be recursively explored for + // more marks later. + if (vm->grayCount >= vm->grayCapacity) + { + vm->grayCapacity = vm->grayCount * 2; + vm->gray = (Obj**)vm->config.reallocateFn(vm->gray, + vm->grayCapacity * sizeof(Obj*), + vm->config.userData); + } + + vm->gray[vm->grayCount++] = obj; +} + +void wrenGrayValue(WrenVM* vm, Value value) +{ + if (!IS_OBJ(value)) return; + wrenGrayObj(vm, AS_OBJ(value)); +} + +void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) +{ + for (int i = 0; i < buffer->count; i++) + { + wrenGrayValue(vm, buffer->data[i]); + } +} + +static void blackenClass(WrenVM* vm, ObjClass* classObj) +{ + // The metaclass. + wrenGrayObj(vm, (Obj*)classObj->obj.classObj); + + // The superclass. + wrenGrayObj(vm, (Obj*)classObj->superclass); + + // Method function objects. + for (int i = 0; i < classObj->methods.count; i++) + { + if (classObj->methods.data[i].type == METHOD_BLOCK) + { + wrenGrayObj(vm, (Obj*)classObj->methods.data[i].as.closure); + } + } + + wrenGrayObj(vm, (Obj*)classObj->name); + + if(!IS_NULL(classObj->attributes)) wrenGrayObj(vm, AS_OBJ(classObj->attributes)); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjClass); + vm->bytesAllocated += classObj->methods.capacity * sizeof(Method); +} + +static void blackenClosure(WrenVM* vm, ObjClosure* closure) +{ + // Mark the function. + wrenGrayObj(vm, (Obj*)closure->fn); + + // Mark the upvalues. + for (int i = 0; i < closure->fn->numUpvalues; i++) + { + wrenGrayObj(vm, (Obj*)closure->upvalues[i]); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjClosure); + vm->bytesAllocated += sizeof(ObjUpvalue*) * closure->fn->numUpvalues; +} + +static void blackenFiber(WrenVM* vm, ObjFiber* fiber) +{ + // Stack functions. + for (int i = 0; i < fiber->numFrames; i++) + { + wrenGrayObj(vm, (Obj*)fiber->frames[i].closure); + } + + // Stack variables. + for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) + { + wrenGrayValue(vm, *slot); + } + + // Open upvalues. + ObjUpvalue* upvalue = fiber->openUpvalues; + while (upvalue != NULL) + { + wrenGrayObj(vm, (Obj*)upvalue); + upvalue = upvalue->next; + } + + // The caller. + wrenGrayObj(vm, (Obj*)fiber->caller); + wrenGrayValue(vm, fiber->error); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjFiber); + vm->bytesAllocated += fiber->frameCapacity * sizeof(CallFrame); + vm->bytesAllocated += fiber->stackCapacity * sizeof(Value); +} + +static void blackenFn(WrenVM* vm, ObjFn* fn) +{ + // Mark the constants. + wrenGrayBuffer(vm, &fn->constants); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjFn); + vm->bytesAllocated += sizeof(uint8_t) * fn->code.capacity; + vm->bytesAllocated += sizeof(Value) * fn->constants.capacity; + + // The debug line number buffer. + vm->bytesAllocated += sizeof(int) * fn->code.capacity; + // TODO: What about the function name? +} + +static void blackenForeign(WrenVM* vm, ObjForeign* foreign) +{ + // TODO: Keep track of how much memory the foreign object uses. We can store + // this in each foreign object, but it will balloon the size. We may not want + // that much overhead. One option would be to let the foreign class register + // a C function that returns a size for the object. That way the VM doesn't + // always have to explicitly store it. +} + +static void blackenInstance(WrenVM* vm, ObjInstance* instance) +{ + wrenGrayObj(vm, (Obj*)instance->obj.classObj); + + // Mark the fields. + for (int i = 0; i < instance->obj.classObj->numFields; i++) + { + wrenGrayValue(vm, instance->fields[i]); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjInstance); + vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; +} + +static void blackenList(WrenVM* vm, ObjList* list) +{ + // Mark the elements. + wrenGrayBuffer(vm, &list->elements); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjList); + vm->bytesAllocated += sizeof(Value) * list->elements.capacity; +} + +static void blackenMap(WrenVM* vm, ObjMap* map) +{ + // Mark the entries. + for (uint32_t i = 0; i < map->capacity; i++) + { + MapEntry* entry = &map->entries[i]; + if (IS_UNDEFINED(entry->key)) continue; + + wrenGrayValue(vm, entry->key); + wrenGrayValue(vm, entry->value); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjMap); + vm->bytesAllocated += sizeof(MapEntry) * map->capacity; +} + +static void blackenModule(WrenVM* vm, ObjModule* module) +{ + // Top-level variables. + for (int i = 0; i < module->variables.count; i++) + { + wrenGrayValue(vm, module->variables.data[i]); + } + + wrenBlackenSymbolTable(vm, &module->variableNames); + + wrenGrayObj(vm, (Obj*)module->name); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjModule); +} + +static void blackenRange(WrenVM* vm, ObjRange* range) +{ + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjRange); +} + +static void blackenString(WrenVM* vm, ObjString* string) +{ + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjString) + string->length + 1; +} + +static void blackenUpvalue(WrenVM* vm, ObjUpvalue* upvalue) +{ + // Mark the closed-over object (in case it is closed). + wrenGrayValue(vm, upvalue->closed); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += sizeof(ObjUpvalue); +} + +static void blackenObject(WrenVM* vm, Obj* obj) +{ +#if WREN_DEBUG_TRACE_MEMORY + printf("mark "); + wrenDumpValue(OBJ_VAL(obj)); + printf(" @ %p\n", obj); +#endif + + // Traverse the object's fields. + switch (obj->type) + { + case OBJ_CLASS: blackenClass( vm, (ObjClass*) obj); break; + case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; + case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; + case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; + case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break; + case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; + case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; + case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; + case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; + case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break; + case OBJ_STRING: blackenString( vm, (ObjString*) obj); break; + case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break; + } +} + +void wrenBlackenObjects(WrenVM* vm) +{ + while (vm->grayCount > 0) + { + // Pop an item from the gray stack. + Obj* obj = vm->gray[--vm->grayCount]; + blackenObject(vm, obj); + } +} + +void wrenFreeObj(WrenVM* vm, Obj* obj) +{ +#if WREN_DEBUG_TRACE_MEMORY + printf("free "); + wrenDumpValue(OBJ_VAL(obj)); + printf(" @ %p\n", obj); +#endif + + switch (obj->type) + { + case OBJ_CLASS: + wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods); + break; + + case OBJ_FIBER: + { + ObjFiber* fiber = (ObjFiber*)obj; + DEALLOCATE(vm, fiber->frames); + DEALLOCATE(vm, fiber->stack); + break; + } + + case OBJ_FN: + { + ObjFn* fn = (ObjFn*)obj; + wrenValueBufferClear(vm, &fn->constants); + wrenByteBufferClear(vm, &fn->code); + wrenIntBufferClear(vm, &fn->debug->sourceLines); + DEALLOCATE(vm, fn->debug->name); + DEALLOCATE(vm, fn->debug); + break; + } + + case OBJ_FOREIGN: + wrenFinalizeForeign(vm, (ObjForeign*)obj); + break; + + case OBJ_LIST: + wrenValueBufferClear(vm, &((ObjList*)obj)->elements); + break; + + case OBJ_MAP: + DEALLOCATE(vm, ((ObjMap*)obj)->entries); + break; + + case OBJ_MODULE: + wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); + wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); + break; + + case OBJ_CLOSURE: + case OBJ_INSTANCE: + case OBJ_RANGE: + case OBJ_STRING: + case OBJ_UPVALUE: + break; + } + + DEALLOCATE(vm, obj); +} + +ObjClass* wrenGetClass(WrenVM* vm, Value value) +{ + return wrenGetClassInline(vm, value); +} + +bool wrenValuesEqual(Value a, Value b) +{ + if (wrenValuesSame(a, b)) return true; + + // If we get here, it's only possible for two heap-allocated immutable objects + // to be equal. + if (!IS_OBJ(a) || !IS_OBJ(b)) return false; + + Obj* aObj = AS_OBJ(a); + Obj* bObj = AS_OBJ(b); + + // Must be the same type. + if (aObj->type != bObj->type) return false; + + switch (aObj->type) + { + case OBJ_RANGE: + { + ObjRange* aRange = (ObjRange*)aObj; + ObjRange* bRange = (ObjRange*)bObj; + return aRange->from == bRange->from && + aRange->to == bRange->to && + aRange->isInclusive == bRange->isInclusive; + } + + case OBJ_STRING: + { + ObjString* aString = (ObjString*)aObj; + ObjString* bString = (ObjString*)bObj; + return aString->hash == bString->hash && + wrenStringEqualsCString(aString, bString->value, bString->length); + } + + default: + // All other types are only equal if they are same, which they aren't if + // we get here. + return false; + } +} +// End file "wren_value.c" +// Begin file "wren_debug.c" +#include + + +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 ? "" : 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"); +} +// End file "wren_debug.c" +// Begin file "wren_compiler.c" +#include +#include +#include +#include + + +#if WREN_DEBUG_DUMP_COMPILED_CODE +#endif + +// This is written in bottom-up order, so the tokenization comes first, then +// parsing/code generation. This minimizes the number of explicit forward +// declarations needed. + +// The maximum number of local (i.e. not module level) variables that can be +// declared in a single function, method, or chunk of top level code. This is +// the maximum number of variables in scope at one time, and spans block scopes. +// +// Note that this limitation is also explicit in the bytecode. Since +// `CODE_LOAD_LOCAL` and `CODE_STORE_LOCAL` use a single argument byte to +// identify the local, only 256 can be in scope at one time. +#define MAX_LOCALS 256 + +// The maximum number of upvalues (i.e. variables from enclosing functions) +// that a function can close over. +#define MAX_UPVALUES 256 + +// The maximum number of distinct constants that a function can contain. This +// value is explicit in the bytecode since `CODE_CONSTANT` only takes a single +// two-byte argument. +#define MAX_CONSTANTS (1 << 16) + +// The maximum distance a CODE_JUMP or CODE_JUMP_IF instruction can move the +// instruction pointer. +#define MAX_JUMP (1 << 16) + +// The maximum depth that interpolation can nest. For example, this string has +// three levels: +// +// "outside %(one + "%(two + "%(three)")")" +#define MAX_INTERPOLATION_NESTING 8 + +// The buffer size used to format a compile error message, excluding the header +// with the module name and error location. Using a hardcoded buffer for this +// is kind of hairy, but fortunately we can control what the longest possible +// message is and handle that. Ideally, we'd use `snprintf()`, but that's not +// available in standard C++98. +#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15) + +typedef enum +{ + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LEFT_BRACE, + TOKEN_RIGHT_BRACE, + TOKEN_COLON, + TOKEN_DOT, + TOKEN_DOTDOT, + TOKEN_DOTDOTDOT, + TOKEN_COMMA, + TOKEN_STAR, + TOKEN_SLASH, + TOKEN_PERCENT, + TOKEN_HASH, + TOKEN_PLUS, + TOKEN_MINUS, + TOKEN_LTLT, + TOKEN_GTGT, + TOKEN_PIPE, + TOKEN_PIPEPIPE, + TOKEN_CARET, + TOKEN_AMP, + TOKEN_AMPAMP, + TOKEN_BANG, + TOKEN_TILDE, + TOKEN_QUESTION, + TOKEN_EQ, + TOKEN_LT, + TOKEN_GT, + TOKEN_LTEQ, + TOKEN_GTEQ, + TOKEN_EQEQ, + TOKEN_BANGEQ, + + TOKEN_BREAK, + TOKEN_CONTINUE, + TOKEN_CLASS, + TOKEN_CONSTRUCT, + TOKEN_ELSE, + TOKEN_FALSE, + TOKEN_FOR, + TOKEN_FOREIGN, + TOKEN_IF, + TOKEN_IMPORT, + TOKEN_AS, + TOKEN_IN, + TOKEN_IS, + TOKEN_NULL, + TOKEN_RETURN, + TOKEN_STATIC, + TOKEN_SUPER, + TOKEN_THIS, + TOKEN_TRUE, + TOKEN_VAR, + TOKEN_WHILE, + + TOKEN_FIELD, + TOKEN_STATIC_FIELD, + TOKEN_NAME, + TOKEN_NUMBER, + + // A string literal without any interpolation, or the last section of a + // string following the last interpolated expression. + TOKEN_STRING, + + // A portion of a string literal preceding an interpolated expression. This + // string: + // + // "a %(b) c %(d) e" + // + // is tokenized to: + // + // TOKEN_INTERPOLATION "a " + // TOKEN_NAME b + // TOKEN_INTERPOLATION " c " + // TOKEN_NAME d + // TOKEN_STRING " e" + TOKEN_INTERPOLATION, + + TOKEN_LINE, + + TOKEN_ERROR, + TOKEN_EOF +} TokenType; + +typedef struct +{ + TokenType type; + + // The beginning of the token, pointing directly into the source. + const char* start; + + // The length of the token in characters. + int length; + + // The 1-based line where the token appears. + int line; + + // The parsed value if the token is a literal. + Value value; +} Token; + +typedef struct +{ + WrenVM* vm; + + // The module being parsed. + ObjModule* module; + + // The source code being parsed. + const char* source; + + // The beginning of the currently-being-lexed token in [source]. + const char* tokenStart; + + // The current character being lexed in [source]. + const char* currentChar; + + // The 1-based line number of [currentChar]. + int currentLine; + + // The upcoming token. + Token next; + + // The most recently lexed token. + Token current; + + // The most recently consumed/advanced token. + Token previous; + + // Tracks the lexing state when tokenizing interpolated strings. + // + // Interpolated strings make the lexer not strictly regular: we don't know + // whether a ")" should be treated as a RIGHT_PAREN token or as ending an + // interpolated expression unless we know whether we are inside a string + // interpolation and how many unmatched "(" there are. This is particularly + // complex because interpolation can nest: + // + // " %( " %( inner ) " ) " + // + // This tracks that state. The parser maintains a stack of ints, one for each + // level of current interpolation nesting. Each value is the number of + // unmatched "(" that are waiting to be closed. + int parens[MAX_INTERPOLATION_NESTING]; + int numParens; + + // Whether compile errors should be printed to stderr or discarded. + bool printErrors; + + // If a syntax or compile error has occurred. + bool hasError; +} Parser; + +typedef struct +{ + // The name of the local variable. This points directly into the original + // source code string. + const char* name; + + // The length of the local variable's name. + int length; + + // The depth in the scope chain that this variable was declared at. Zero is + // the outermost scope--parameters for a method, or the first local block in + // top level code. One is the scope within that, etc. + int depth; + + // If this local variable is being used as an upvalue. + bool isUpvalue; +} Local; + +typedef struct +{ + // True if this upvalue is capturing a local variable from the enclosing + // function. False if it's capturing an upvalue. + bool isLocal; + + // The index of the local or upvalue being captured in the enclosing function. + int index; +} CompilerUpvalue; + +// Bookkeeping information for the current loop being compiled. +typedef struct sLoop +{ + // Index of the instruction that the loop should jump back to. + int start; + + // Index of the argument for the CODE_JUMP_IF instruction used to exit the + // loop. Stored so we can patch it once we know where the loop ends. + int exitJump; + + // Index of the first instruction of the body of the loop. + int body; + + // Depth of the scope(s) that need to be exited if a break is hit inside the + // loop. + int scopeDepth; + + // The loop enclosing this one, or NULL if this is the outermost loop. + struct sLoop* enclosing; +} Loop; + +// The different signature syntaxes for different kinds of methods. +typedef enum +{ + // A name followed by a (possibly empty) parenthesized parameter list. Also + // used for binary operators. + SIG_METHOD, + + // Just a name. Also used for unary operators. + SIG_GETTER, + + // A name followed by "=". + SIG_SETTER, + + // A square bracketed parameter list. + SIG_SUBSCRIPT, + + // A square bracketed parameter list followed by "=". + SIG_SUBSCRIPT_SETTER, + + // A constructor initializer function. This has a distinct signature to + // prevent it from being invoked directly outside of the constructor on the + // metaclass. + SIG_INITIALIZER +} SignatureType; + +typedef struct +{ + const char* name; + int length; + SignatureType type; + int arity; +} Signature; + +// Bookkeeping information for compiling a class definition. +typedef struct +{ + // The name of the class. + ObjString* name; + + // Attributes for the class itself + ObjMap* classAttributes; + // Attributes for methods in this class + ObjMap* methodAttributes; + + // Symbol table for the fields of the class. + SymbolTable fields; + + // Symbols for the methods defined by the class. Used to detect duplicate + // method definitions. + IntBuffer methods; + IntBuffer staticMethods; + + // True if the class being compiled is a foreign class. + bool isForeign; + + // True if the current method being compiled is static. + bool inStatic; + + // The signature of the method being compiled. + Signature* signature; +} ClassInfo; + +struct sCompiler +{ + Parser* parser; + + // The compiler for the function enclosing this one, or NULL if it's the + // top level. + struct sCompiler* parent; + + // The currently in scope local variables. + Local locals[MAX_LOCALS]; + + // The number of local variables currently in scope. + int numLocals; + + // The upvalues that this function has captured from outer scopes. The count + // of them is stored in [numUpvalues]. + CompilerUpvalue upvalues[MAX_UPVALUES]; + + // The current level of block scope nesting, where zero is no nesting. A -1 + // here means top-level code is being compiled and there is no block scope + // in effect at all. Any variables declared will be module-level. + int scopeDepth; + + // The current number of slots (locals and temporaries) in use. + // + // We use this and maxSlots to track the maximum number of additional slots + // a function may need while executing. When the function is called, the + // fiber will check to ensure its stack has enough room to cover that worst + // case and grow the stack if needed. + // + // This value here doesn't include parameters to the function. Since those + // are already pushed onto the stack by the caller and tracked there, we + // don't need to double count them here. + int numSlots; + + // The current innermost loop being compiled, or NULL if not in a loop. + Loop* loop; + + // If this is a compiler for a method, keeps track of the class enclosing it. + ClassInfo* enclosingClass; + + // The function being compiled. + ObjFn* fn; + + // The constants for the function being compiled. + ObjMap* constants; + + // Whether or not the compiler is for a constructor initializer + bool isInitializer; + + // The number of attributes seen while parsing. + // We track this separately as compile time attributes + // are not stored, so we can't rely on attributes->count + // to enforce an error message when attributes are used + // anywhere other than methods or classes. + int numAttributes; + // Attributes for the next class or method. + ObjMap* attributes; +}; + +// Describes where a variable is declared. +typedef enum +{ + // A local variable in the current function. + SCOPE_LOCAL, + + // A local variable declared in an enclosing function. + SCOPE_UPVALUE, + + // A top-level module variable. + SCOPE_MODULE +} Scope; + +// A reference to a variable and the scope where it is defined. This contains +// enough information to emit correct code to load or store the variable. +typedef struct +{ + // The stack slot, upvalue slot, or module symbol defining the variable. + int index; + + // Where the variable is declared. + Scope scope; +} Variable; + +// Forward declarations +static void disallowAttributes(Compiler* compiler); +static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value); +static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo); +static void copyAttributes(Compiler* compiler, ObjMap* into); +static void copyMethodAttributes(Compiler* compiler, bool isForeign, + bool isStatic, const char* fullSignature, int32_t length); + +// The stack effect of each opcode. The index in the array is the opcode, and +// the value is the stack effect of that instruction. +static const int stackEffects[] = { + #define OPCODE(_, effect) effect, +// Begin file "wren_opcodes.h" +// 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) +// End file "wren_opcodes.h" + #undef OPCODE +}; + +static void printError(Parser* parser, int line, const char* label, + const char* format, va_list args) +{ + parser->hasError = true; + if (!parser->printErrors) return; + + // Only report errors if there is a WrenErrorFn to handle them. + if (parser->vm->config.errorFn == NULL) return; + + // Format the label and message. + char message[ERROR_MESSAGE_SIZE]; + int length = sprintf(message, "%s: ", label); + length += vsprintf(message + length, format, args); + ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer."); + + ObjString* module = parser->module->name; + const char* module_name = module ? module->value : ""; + + parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE, + module_name, line, message); +} + +// Outputs a lexical error. +static void lexError(Parser* parser, const char* format, ...) +{ + va_list args; + va_start(args, format); + printError(parser, parser->currentLine, "Error", format, args); + va_end(args); +} + +// Outputs a compile or syntax error. This also marks the compilation as having +// an error, which ensures that the resulting code will be discarded and never +// run. This means that after calling error(), it's fine to generate whatever +// invalid bytecode you want since it won't be used. +// +// You'll note that most places that call error() continue to parse and compile +// after that. That's so that we can try to find as many compilation errors in +// one pass as possible instead of just bailing at the first one. +static void error(Compiler* compiler, const char* format, ...) +{ + Token* token = &compiler->parser->previous; + + // If the parse error was caused by an error token, the lexer has already + // reported it. + if (token->type == TOKEN_ERROR) return; + + va_list args; + va_start(args, format); + if (token->type == TOKEN_LINE) + { + printError(compiler->parser, token->line, "Error at newline", format, args); + } + else if (token->type == TOKEN_EOF) + { + printError(compiler->parser, token->line, + "Error at end of file", format, args); + } + else + { + // Make sure we don't exceed the buffer with a very long token. + char label[10 + MAX_VARIABLE_NAME + 4 + 1]; + if (token->length <= MAX_VARIABLE_NAME) + { + sprintf(label, "Error at '%.*s'", token->length, token->start); + } + else + { + sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start); + } + printError(compiler->parser, token->line, label, format, args); + } + va_end(args); +} + +// Adds [constant] to the constant pool and returns its index. +static int addConstant(Compiler* compiler, Value constant) +{ + if (compiler->parser->hasError) return -1; + + // See if we already have a constant for the value. If so, reuse it. + if (compiler->constants != NULL) + { + Value existing = wrenMapGet(compiler->constants, constant); + if (IS_NUM(existing)) return (int)AS_NUM(existing); + } + + // It's a new constant. + if (compiler->fn->constants.count < MAX_CONSTANTS) + { + if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); + wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants, + constant); + if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm); + + if (compiler->constants == NULL) + { + compiler->constants = wrenNewMap(compiler->parser->vm); + } + wrenMapSet(compiler->parser->vm, compiler->constants, constant, + NUM_VAL(compiler->fn->constants.count - 1)); + } + else + { + error(compiler, "A function may only contain %d unique constants.", + MAX_CONSTANTS); + } + + return compiler->fn->constants.count - 1; +} + +// Initializes [compiler]. +static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, + bool isMethod) +{ + compiler->parser = parser; + compiler->parent = parent; + compiler->loop = NULL; + compiler->enclosingClass = NULL; + compiler->isInitializer = false; + + // Initialize these to NULL before allocating in case a GC gets triggered in + // the middle of initializing the compiler. + compiler->fn = NULL; + compiler->constants = NULL; + compiler->attributes = NULL; + + parser->vm->compiler = compiler; + + // Declare a local slot for either the closure or method receiver so that we + // don't try to reuse that slot for a user-defined local variable. For + // methods, we name it "this", so that we can resolve references to that like + // a normal variable. For functions, they have no explicit "this", so we use + // an empty name. That way references to "this" inside a function walks up + // the parent chain to find a method enclosing the function whose "this" we + // can close over. + compiler->numLocals = 1; + compiler->numSlots = compiler->numLocals; + + if (isMethod) + { + compiler->locals[0].name = "this"; + compiler->locals[0].length = 4; + } + else + { + compiler->locals[0].name = NULL; + compiler->locals[0].length = 0; + } + + compiler->locals[0].depth = -1; + compiler->locals[0].isUpvalue = false; + + if (parent == NULL) + { + // Compiling top-level code, so the initial scope is module-level. + compiler->scopeDepth = -1; + } + else + { + // The initial scope for functions and methods is local scope. + compiler->scopeDepth = 0; + } + + compiler->numAttributes = 0; + compiler->attributes = wrenNewMap(parser->vm); + compiler->fn = wrenNewFunction(parser->vm, parser->module, + compiler->numLocals); +} + +// Lexing ---------------------------------------------------------------------- + +typedef struct +{ + const char* identifier; + size_t length; + TokenType tokenType; +} Keyword; + +// The table of reserved words and their associated token types. +static Keyword keywords[] = +{ + {"break", 5, TOKEN_BREAK}, + {"continue", 8, TOKEN_CONTINUE}, + {"class", 5, TOKEN_CLASS}, + {"construct", 9, TOKEN_CONSTRUCT}, + {"else", 4, TOKEN_ELSE}, + {"false", 5, TOKEN_FALSE}, + {"for", 3, TOKEN_FOR}, + {"foreign", 7, TOKEN_FOREIGN}, + {"if", 2, TOKEN_IF}, + {"import", 6, TOKEN_IMPORT}, + {"as", 2, TOKEN_AS}, + {"in", 2, TOKEN_IN}, + {"is", 2, TOKEN_IS}, + {"null", 4, TOKEN_NULL}, + {"return", 6, TOKEN_RETURN}, + {"static", 6, TOKEN_STATIC}, + {"super", 5, TOKEN_SUPER}, + {"this", 4, TOKEN_THIS}, + {"true", 4, TOKEN_TRUE}, + {"var", 3, TOKEN_VAR}, + {"while", 5, TOKEN_WHILE}, + {NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array. +}; + +// Returns true if [c] is a valid (non-initial) identifier character. +static bool isName(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; +} + +// Returns true if [c] is a digit. +static bool isDigit(char c) +{ + return c >= '0' && c <= '9'; +} + +// Returns the current character the parser is sitting on. +static char peekChar(Parser* parser) +{ + return *parser->currentChar; +} + +// Returns the character after the current character. +static char peekNextChar(Parser* parser) +{ + // If we're at the end of the source, don't read past it. + if (peekChar(parser) == '\0') return '\0'; + return *(parser->currentChar + 1); +} + +// Advances the parser forward one character. +static char nextChar(Parser* parser) +{ + char c = peekChar(parser); + parser->currentChar++; + if (c == '\n') parser->currentLine++; + return c; +} + +// If the current character is [c], consumes it and returns `true`. +static bool matchChar(Parser* parser, char c) +{ + if (peekChar(parser) != c) return false; + nextChar(parser); + return true; +} + +// Sets the parser's current token to the given [type] and current character +// range. +static void makeToken(Parser* parser, TokenType type) +{ + parser->next.type = type; + parser->next.start = parser->tokenStart; + parser->next.length = (int)(parser->currentChar - parser->tokenStart); + parser->next.line = parser->currentLine; + + // Make line tokens appear on the line containing the "\n". + if (type == TOKEN_LINE) parser->next.line--; +} + +// If the current character is [c], then consumes it and makes a token of type +// [two]. Otherwise makes a token of type [one]. +static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one) +{ + makeToken(parser, matchChar(parser, c) ? two : one); +} + +// Skips the rest of the current line. +static void skipLineComment(Parser* parser) +{ + while (peekChar(parser) != '\n' && peekChar(parser) != '\0') + { + nextChar(parser); + } +} + +// Skips the rest of a block comment. +static void skipBlockComment(Parser* parser) +{ + int nesting = 1; + while (nesting > 0) + { + if (peekChar(parser) == '\0') + { + lexError(parser, "Unterminated block comment."); + return; + } + + if (peekChar(parser) == '/' && peekNextChar(parser) == '*') + { + nextChar(parser); + nextChar(parser); + nesting++; + continue; + } + + if (peekChar(parser) == '*' && peekNextChar(parser) == '/') + { + nextChar(parser); + nextChar(parser); + nesting--; + continue; + } + + // Regular comment character. + nextChar(parser); + } +} + +// Reads the next character, which should be a hex digit (0-9, a-f, or A-F) and +// returns its numeric value. If the character isn't a hex digit, returns -1. +static int readHexDigit(Parser* parser) +{ + char c = nextChar(parser); + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + + // Don't consume it if it isn't expected. Keeps us from reading past the end + // of an unterminated string. + parser->currentChar--; + return -1; +} + +// Parses the numeric value of the current token. +static void makeNumber(Parser* parser, bool isHex) +{ + errno = 0; + + if (isHex) + { + parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16)); + } + else + { + parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL)); + } + + if (errno == ERANGE) + { + lexError(parser, "Number literal was too large (%d).", sizeof(long int)); + parser->next.value = NUM_VAL(0); + } + + // We don't check that the entire token is consumed after calling strtoll() + // or strtod() because we've already scanned it ourselves and know it's valid. + + makeToken(parser, TOKEN_NUMBER); +} + +// Finishes lexing a hexadecimal number literal. +static void readHexNumber(Parser* parser) +{ + // Skip past the `x` used to denote a hexadecimal literal. + nextChar(parser); + + // Iterate over all the valid hexadecimal digits found. + while (readHexDigit(parser) != -1) continue; + + makeNumber(parser, true); +} + +// Finishes lexing a number literal. +static void readNumber(Parser* parser) +{ + while (isDigit(peekChar(parser))) nextChar(parser); + + // See if it has a floating point. Make sure there is a digit after the "." + // so we don't get confused by method calls on number literals. + if (peekChar(parser) == '.' && isDigit(peekNextChar(parser))) + { + nextChar(parser); + while (isDigit(peekChar(parser))) nextChar(parser); + } + + // See if the number is in scientific notation. + if (matchChar(parser, 'e') || matchChar(parser, 'E')) + { + // Allow a single positive/negative exponent symbol. + if(!matchChar(parser, '+')) + { + matchChar(parser, '-'); + } + + if (!isDigit(peekChar(parser))) + { + lexError(parser, "Unterminated scientific notation."); + } + + while (isDigit(peekChar(parser))) nextChar(parser); + } + + makeNumber(parser, false); +} + +// Finishes lexing an identifier. Handles reserved words. +static void readName(Parser* parser, TokenType type, char firstChar) +{ + ByteBuffer string; + wrenByteBufferInit(&string); + wrenByteBufferWrite(parser->vm, &string, firstChar); + + while (isName(peekChar(parser)) || isDigit(peekChar(parser))) + { + char c = nextChar(parser); + wrenByteBufferWrite(parser->vm, &string, c); + } + + // Update the type if it's a keyword. + size_t length = parser->currentChar - parser->tokenStart; + for (int i = 0; keywords[i].identifier != NULL; i++) + { + if (length == keywords[i].length && + memcmp(parser->tokenStart, keywords[i].identifier, length) == 0) + { + type = keywords[i].tokenType; + break; + } + } + + parser->next.value = wrenNewStringLength(parser->vm, + (char*)string.data, string.count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Reads [digits] hex digits in a string literal and returns their number value. +static int readHexEscape(Parser* parser, int digits, const char* description) +{ + int value = 0; + for (int i = 0; i < digits; i++) + { + if (peekChar(parser) == '"' || peekChar(parser) == '\0') + { + lexError(parser, "Incomplete %s escape sequence.", description); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + int digit = readHexDigit(parser); + if (digit == -1) + { + lexError(parser, "Invalid %s escape sequence.", description); + break; + } + + value = (value * 16) | digit; + } + + return value; +} + +// Reads a hex digit Unicode escape sequence in a string literal. +static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length) +{ + int value = readHexEscape(parser, length, "Unicode"); + + // Grow the buffer enough for the encoded result. + int numBytes = wrenUtf8EncodeNumBytes(value); + if (numBytes != 0) + { + wrenByteBufferFill(parser->vm, string, 0, numBytes); + wrenUtf8Encode(value, string->data + string->count - numBytes); + } +} + +static void readRawString(Parser* parser) +{ + ByteBuffer string; + wrenByteBufferInit(&string); + TokenType type = TOKEN_STRING; + + //consume the second and third " + nextChar(parser); + nextChar(parser); + + int skipStart = 0; + int firstNewline = -1; + + int skipEnd = -1; + int lastNewline = -1; + + for (;;) + { + char c = nextChar(parser); + char c1 = peekChar(parser); + char c2 = peekNextChar(parser); + + if (c == '\r') continue; + + if (c == '\n') { + lastNewline = string.count; + skipEnd = lastNewline; + firstNewline = firstNewline == -1 ? string.count : firstNewline; + } + + if (c == '"' && c1 == '"' && c2 == '"') break; + + bool isWhitespace = c == ' ' || c == '\t'; + skipEnd = c == '\n' || isWhitespace ? skipEnd : -1; + + // If we haven't seen a newline or other character yet, + // and still seeing whitespace, count the characters + // as skippable till we know otherwise + bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1; + skipStart = skippable ? string.count + 1 : skipStart; + + // We've counted leading whitespace till we hit something else, + // but it's not a newline, so we reset skipStart since we need these characters + if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1; + + if (c == '\0' || c1 == '\0' || c2 == '\0') + { + lexError(parser, "Unterminated raw string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + wrenByteBufferWrite(parser->vm, &string, c); + } + + //consume the second and third " + nextChar(parser); + nextChar(parser); + + int offset = 0; + int count = string.count; + + if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1; + if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline; + + count -= (offset > count) ? count : offset; + + parser->next.value = wrenNewStringLength(parser->vm, + ((char*)string.data) + offset, count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Finishes lexing a string literal. +static void readString(Parser* parser) +{ + ByteBuffer string; + TokenType type = TOKEN_STRING; + wrenByteBufferInit(&string); + + for (;;) + { + char c = nextChar(parser); + if (c == '"') break; + if (c == '\r') continue; + + if (c == '\0') + { + lexError(parser, "Unterminated string."); + + // Don't consume it if it isn't expected. Keeps us from reading past the + // end of an unterminated string. + parser->currentChar--; + break; + } + + if (c == '%') + { + if (parser->numParens < MAX_INTERPOLATION_NESTING) + { + // TODO: Allow format string. + if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'."); + + parser->parens[parser->numParens++] = 1; + type = TOKEN_INTERPOLATION; + break; + } + + lexError(parser, "Interpolation may only nest %d levels deep.", + MAX_INTERPOLATION_NESTING); + } + + if (c == '\\') + { + switch (nextChar(parser)) + { + case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break; + case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break; + case '%': wrenByteBufferWrite(parser->vm, &string, '%'); break; + case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break; + case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break; + case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break; + case 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); break; + case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break; + case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break; + case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break; + case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break; + case 'u': readUnicodeEscape(parser, &string, 4); break; + case 'U': readUnicodeEscape(parser, &string, 8); break; + case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break; + case 'x': + wrenByteBufferWrite(parser->vm, &string, + (uint8_t)readHexEscape(parser, 2, "byte")); + break; + + default: + lexError(parser, "Invalid escape character '%c'.", + *(parser->currentChar - 1)); + break; + } + } + else + { + wrenByteBufferWrite(parser->vm, &string, c); + } + } + + parser->next.value = wrenNewStringLength(parser->vm, + (char*)string.data, string.count); + + wrenByteBufferClear(parser->vm, &string); + makeToken(parser, type); +} + +// Lex the next token and store it in [parser.next]. +static void nextToken(Parser* parser) +{ + parser->previous = parser->current; + parser->current = parser->next; + + // If we are out of tokens, don't try to tokenize any more. We *do* still + // copy the TOKEN_EOF to previous so that code that expects it to be consumed + // will still work. + if (parser->next.type == TOKEN_EOF) return; + if (parser->current.type == TOKEN_EOF) return; + + while (peekChar(parser) != '\0') + { + parser->tokenStart = parser->currentChar; + + char c = nextChar(parser); + switch (c) + { + case '(': + // If we are inside an interpolated expression, count the unmatched "(". + if (parser->numParens > 0) parser->parens[parser->numParens - 1]++; + makeToken(parser, TOKEN_LEFT_PAREN); + return; + + case ')': + // If we are inside an interpolated expression, count the ")". + if (parser->numParens > 0 && + --parser->parens[parser->numParens - 1] == 0) + { + // This is the final ")", so the interpolation expression has ended. + // This ")" now begins the next section of the template string. + parser->numParens--; + readString(parser); + return; + } + + makeToken(parser, TOKEN_RIGHT_PAREN); + return; + + case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return; + case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return; + case '{': makeToken(parser, TOKEN_LEFT_BRACE); return; + case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; + case ':': makeToken(parser, TOKEN_COLON); return; + case ',': makeToken(parser, TOKEN_COMMA); return; + case '*': makeToken(parser, TOKEN_STAR); return; + case '%': makeToken(parser, TOKEN_PERCENT); return; + case '#': { + // Ignore shebang on the first line. + if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/') + { + skipLineComment(parser); + break; + } + // Otherwise we treat it as a token + makeToken(parser, TOKEN_HASH); + return; + } + case '^': makeToken(parser, TOKEN_CARET); return; + case '+': makeToken(parser, TOKEN_PLUS); return; + case '-': makeToken(parser, TOKEN_MINUS); return; + case '~': makeToken(parser, TOKEN_TILDE); return; + case '?': makeToken(parser, TOKEN_QUESTION); return; + + case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; + case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; + case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; + case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; + + case '.': + if (matchChar(parser, '.')) + { + twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); + return; + } + + makeToken(parser, TOKEN_DOT); + return; + + case '/': + if (matchChar(parser, '/')) + { + skipLineComment(parser); + break; + } + + if (matchChar(parser, '*')) + { + skipBlockComment(parser); + break; + } + + makeToken(parser, TOKEN_SLASH); + return; + + case '<': + if (matchChar(parser, '<')) + { + makeToken(parser, TOKEN_LTLT); + } + else + { + twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT); + } + return; + + case '>': + if (matchChar(parser, '>')) + { + makeToken(parser, TOKEN_GTGT); + } + else + { + twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT); + } + return; + + case '\n': + makeToken(parser, TOKEN_LINE); + return; + + case ' ': + case '\r': + case '\t': + // Skip forward until we run out of whitespace. + while (peekChar(parser) == ' ' || + peekChar(parser) == '\r' || + peekChar(parser) == '\t') + { + nextChar(parser); + } + break; + + case '"': { + if(peekChar(parser) == '"' && peekNextChar(parser) == '"') { + readRawString(parser); + return; + } + readString(parser); return; + } + case '_': + readName(parser, + peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c); + return; + + case '0': + if (peekChar(parser) == 'x') + { + readHexNumber(parser); + return; + } + + readNumber(parser); + return; + + default: + if (isName(c)) + { + readName(parser, TOKEN_NAME, c); + } + else if (isDigit(c)) + { + readNumber(parser); + } + else + { + if (c >= 32 && c <= 126) + { + lexError(parser, "Invalid character '%c'.", c); + } + else + { + // Don't show non-ASCII values since we didn't UTF-8 decode the + // bytes. Since there are no non-ASCII byte values that are + // meaningful code units in Wren, the lexer works on raw bytes, + // even though the source code and console output are UTF-8. + lexError(parser, "Invalid byte 0x%x.", (uint8_t)c); + } + parser->next.type = TOKEN_ERROR; + parser->next.length = 0; + } + return; + } + } + + // If we get here, we're out of source, so just make EOF tokens. + parser->tokenStart = parser->currentChar; + makeToken(parser, TOKEN_EOF); +} + +// Parsing --------------------------------------------------------------------- + +// Returns the type of the current token. +static TokenType peek(Compiler* compiler) +{ + return compiler->parser->current.type; +} + +// Returns the type of the current token. +static TokenType peekNext(Compiler* compiler) +{ + return compiler->parser->next.type; +} + +// Consumes the current token if its type is [expected]. Returns true if a +// token was consumed. +static bool match(Compiler* compiler, TokenType expected) +{ + if (peek(compiler) != expected) return false; + + nextToken(compiler->parser); + return true; +} + +// Consumes the current token. Emits an error if its type is not [expected]. +static void consume(Compiler* compiler, TokenType expected, + const char* errorMessage) +{ + nextToken(compiler->parser); + if (compiler->parser->previous.type != expected) + { + error(compiler, errorMessage); + + // If the next token is the one we want, assume the current one is just a + // spurious error and discard it to minimize the number of cascaded errors. + if (compiler->parser->current.type == expected) nextToken(compiler->parser); + } +} + +// Matches one or more newlines. Returns true if at least one was found. +static bool matchLine(Compiler* compiler) +{ + if (!match(compiler, TOKEN_LINE)) return false; + + while (match(compiler, TOKEN_LINE)); + return true; +} + +// Discards any newlines starting at the current token. +static void ignoreNewlines(Compiler* compiler) +{ + matchLine(compiler); +} + +// Consumes the current token. Emits an error if it is not a newline. Then +// discards any duplicate newlines following it. +static void consumeLine(Compiler* compiler, const char* errorMessage) +{ + consume(compiler, TOKEN_LINE, errorMessage); + ignoreNewlines(compiler); +} + +static void allowLineBeforeDot(Compiler* compiler) { + if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) { + nextToken(compiler->parser); + } +} + +// Variables and scopes -------------------------------------------------------- + +// Emits one single-byte argument. Returns its index. +static int emitByte(Compiler* compiler, int byte) +{ + wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte); + + // Assume the instruction is associated with the most recently consumed token. + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines, + compiler->parser->previous.line); + + return compiler->fn->code.count - 1; +} + +// Emits one bytecode instruction. +static void emitOp(Compiler* compiler, Code instruction) +{ + emitByte(compiler, instruction); + + // Keep track of the stack's high water mark. + compiler->numSlots += stackEffects[instruction]; + if (compiler->numSlots > compiler->fn->maxSlots) + { + compiler->fn->maxSlots = compiler->numSlots; + } +} + +// Emits one 16-bit argument, which will be written big endian. +static void emitShort(Compiler* compiler, int arg) +{ + emitByte(compiler, (arg >> 8) & 0xff); + emitByte(compiler, arg & 0xff); +} + +// Emits one bytecode instruction followed by a 8-bit argument. Returns the +// index of the argument in the bytecode. +static int emitByteArg(Compiler* compiler, Code instruction, int arg) +{ + emitOp(compiler, instruction); + return emitByte(compiler, arg); +} + +// Emits one bytecode instruction followed by a 16-bit argument, which will be +// written big endian. +static void emitShortArg(Compiler* compiler, Code instruction, int arg) +{ + emitOp(compiler, instruction); + emitShort(compiler, arg); +} + +// Emits [instruction] followed by a placeholder for a jump offset. The +// placeholder can be patched by calling [jumpPatch]. Returns the index of the +// placeholder. +static int emitJump(Compiler* compiler, Code instruction) +{ + emitOp(compiler, instruction); + emitByte(compiler, 0xff); + return emitByte(compiler, 0xff) - 1; +} + +// Creates a new constant for the current value and emits the bytecode to load +// it from the constant table. +static void emitConstant(Compiler* compiler, Value value) +{ + int constant = addConstant(compiler, value); + + // Compile the code to load the constant. + emitShortArg(compiler, CODE_CONSTANT, constant); +} + +// Create a new local variable with [name]. Assumes the current scope is local +// and the name is unique. +static int addLocal(Compiler* compiler, const char* name, int length) +{ + Local* local = &compiler->locals[compiler->numLocals]; + local->name = name; + local->length = length; + local->depth = compiler->scopeDepth; + local->isUpvalue = false; + return compiler->numLocals++; +} + +// Declares a variable in the current scope whose name is the given token. +// +// If [token] is `NULL`, uses the previously consumed token. Returns its symbol. +static int declareVariable(Compiler* compiler, Token* token) +{ + if (token == NULL) token = &compiler->parser->previous; + + if (token->length > MAX_VARIABLE_NAME) + { + error(compiler, "Variable name cannot be longer than %d characters.", + MAX_VARIABLE_NAME); + } + + // Top-level module scope. + if (compiler->scopeDepth == -1) + { + int line = -1; + int symbol = wrenDefineVariable(compiler->parser->vm, + compiler->parser->module, + token->start, token->length, + NULL_VAL, &line); + + if (symbol == -1) + { + error(compiler, "Module variable is already defined."); + } + else if (symbol == -2) + { + error(compiler, "Too many module variables defined."); + } + else if (symbol == -3) + { + error(compiler, + "Variable '%.*s' referenced before this definition (first use at line %d).", + token->length, token->start, line); + } + + return symbol; + } + + // See if there is already a variable with this name declared in the current + // scope. (Outer scopes are OK: those get shadowed.) + for (int i = compiler->numLocals - 1; i >= 0; i--) + { + Local* local = &compiler->locals[i]; + + // Once we escape this scope and hit an outer one, we can stop. + if (local->depth < compiler->scopeDepth) break; + + if (local->length == token->length && + memcmp(local->name, token->start, token->length) == 0) + { + error(compiler, "Variable is already declared in this scope."); + return i; + } + } + + if (compiler->numLocals == MAX_LOCALS) + { + error(compiler, "Cannot declare more than %d variables in one scope.", + MAX_LOCALS); + return -1; + } + + return addLocal(compiler, token->start, token->length); +} + +// Parses a name token and declares a variable in the current scope with that +// name. Returns its slot. +static int declareNamedVariable(Compiler* compiler) +{ + consume(compiler, TOKEN_NAME, "Expect variable name."); + return declareVariable(compiler, NULL); +} + +// Stores a variable with the previously defined symbol in the current scope. +static void defineVariable(Compiler* compiler, int symbol) +{ + // Store the variable. If it's a local, the result of the initializer is + // in the correct slot on the stack already so we're done. + if (compiler->scopeDepth >= 0) return; + + // It's a module-level variable, so store the value in the module slot and + // then discard the temporary for the initializer. + emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol); + emitOp(compiler, CODE_POP); +} + +// Starts a new local block scope. +static void pushScope(Compiler* compiler) +{ + compiler->scopeDepth++; +} + +// Generates code to discard local variables at [depth] or greater. Does *not* +// actually undeclare variables or pop any scopes, though. This is called +// directly when compiling "break" statements to ditch the local variables +// before jumping out of the loop even though they are still in scope *past* +// the break instruction. +// +// Returns the number of local variables that were eliminated. +static int discardLocals(Compiler* compiler, int depth) +{ + ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope."); + + int local = compiler->numLocals - 1; + while (local >= 0 && compiler->locals[local].depth >= depth) + { + // If the local was closed over, make sure the upvalue gets closed when it + // goes out of scope on the stack. We use emitByte() and not emitOp() here + // because we don't want to track that stack effect of these pops since the + // variables are still in scope after the break. + if (compiler->locals[local].isUpvalue) + { + emitByte(compiler, CODE_CLOSE_UPVALUE); + } + else + { + emitByte(compiler, CODE_POP); + } + + + local--; + } + + return compiler->numLocals - local - 1; +} + +// Closes the last pushed block scope and discards any local variables declared +// in that scope. This should only be called in a statement context where no +// temporaries are still on the stack. +static void popScope(Compiler* compiler) +{ + int popped = discardLocals(compiler, compiler->scopeDepth); + compiler->numLocals -= popped; + compiler->numSlots -= popped; + compiler->scopeDepth--; +} + +// Attempts to look up the name in the local variables of [compiler]. If found, +// returns its index, otherwise returns -1. +static int resolveLocal(Compiler* compiler, const char* name, int length) +{ + // Look it up in the local scopes. Look in reverse order so that the most + // nested variable is found first and shadows outer ones. + for (int i = compiler->numLocals - 1; i >= 0; i--) + { + if (compiler->locals[i].length == length && + memcmp(name, compiler->locals[i].name, length) == 0) + { + return i; + } + } + + return -1; +} + +// Adds an upvalue to [compiler]'s function with the given properties. Does not +// add one if an upvalue for that variable is already in the list. Returns the +// index of the upvalue. +static int addUpvalue(Compiler* compiler, bool isLocal, int index) +{ + // Look for an existing one. + for (int i = 0; i < compiler->fn->numUpvalues; i++) + { + CompilerUpvalue* upvalue = &compiler->upvalues[i]; + if (upvalue->index == index && upvalue->isLocal == isLocal) return i; + } + + // If we got here, it's a new upvalue. + compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal; + compiler->upvalues[compiler->fn->numUpvalues].index = index; + return compiler->fn->numUpvalues++; +} + +// Attempts to look up [name] in the functions enclosing the one being compiled +// by [compiler]. If found, it adds an upvalue for it to this compiler's list +// of upvalues (unless it's already in there) and returns its index. If not +// found, returns -1. +// +// If the name is found outside of the immediately enclosing function, this +// will flatten the closure and add upvalues to all of the intermediate +// functions so that it gets walked down to this one. +// +// If it reaches a method boundary, this stops and returns -1 since methods do +// not close over local variables. +static int findUpvalue(Compiler* compiler, const char* name, int length) +{ + // If we are at the top level, we didn't find it. + if (compiler->parent == NULL) return -1; + + // If we hit the method boundary (and the name isn't a static field), then + // stop looking for it. We'll instead treat it as a self send. + if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1; + + // See if it's a local variable in the immediately enclosing function. + int local = resolveLocal(compiler->parent, name, length); + if (local != -1) + { + // Mark the local as an upvalue so we know to close it when it goes out of + // scope. + compiler->parent->locals[local].isUpvalue = true; + + return addUpvalue(compiler, true, local); + } + + // See if it's an upvalue in the immediately enclosing function. In other + // words, if it's a local variable in a non-immediately enclosing function. + // This "flattens" closures automatically: it adds upvalues to all of the + // intermediate functions to get from the function where a local is declared + // all the way into the possibly deeply nested function that is closing over + // it. + int upvalue = findUpvalue(compiler->parent, name, length); + if (upvalue != -1) + { + return addUpvalue(compiler, false, upvalue); + } + + // If we got here, we walked all the way up the parent chain and couldn't + // find it. + return -1; +} + +// Look up [name] in the current scope to see what variable it refers to. +// Returns the variable either in local scope, or the enclosing function's +// upvalue list. Does not search the module scope. Returns a variable with +// index -1 if not found. +static Variable resolveNonmodule(Compiler* compiler, + const char* name, int length) +{ + // Look it up in the local scopes. + Variable variable; + variable.scope = SCOPE_LOCAL; + variable.index = resolveLocal(compiler, name, length); + if (variable.index != -1) return variable; + + // Tt's not a local, so guess that it's an upvalue. + variable.scope = SCOPE_UPVALUE; + variable.index = findUpvalue(compiler, name, length); + return variable; +} + +// Look up [name] in the current scope to see what variable it refers to. +// Returns the variable either in module scope, local scope, or the enclosing +// function's upvalue list. Returns a variable with index -1 if not found. +static Variable resolveName(Compiler* compiler, const char* name, int length) +{ + Variable variable = resolveNonmodule(compiler, name, length); + if (variable.index != -1) return variable; + + variable.scope = SCOPE_MODULE; + variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, + name, length); + return variable; +} + +static void loadLocal(Compiler* compiler, int slot) +{ + if (slot <= 8) + { + emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot)); + return; + } + + emitByteArg(compiler, CODE_LOAD_LOCAL, slot); +} + +// Finishes [compiler], which is compiling a function, method, or chunk of top +// level code. If there is a parent compiler, then this emits code in the +// parent compiler to load the resulting function. +static ObjFn* endCompiler(Compiler* compiler, + const char* debugName, int debugNameLength) +{ + // If we hit an error, don't finish the function since it's borked anyway. + if (compiler->parser->hasError) + { + compiler->parser->vm->compiler = compiler->parent; + return NULL; + } + + // Mark the end of the bytecode. Since it may contain multiple early returns, + // we can't rely on CODE_RETURN to tell us we're at the end. + emitOp(compiler, CODE_END); + + wrenFunctionBindName(compiler->parser->vm, compiler->fn, + debugName, debugNameLength); + + // In the function that contains this one, load the resulting function object. + if (compiler->parent != NULL) + { + int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn)); + + // Wrap the function in a closure. We do this even if it has no upvalues so + // that the VM can uniformly assume all called objects are closures. This + // makes creating a function a little slower, but makes invoking them + // faster. Given that functions are invoked more often than they are + // created, this is a win. + emitShortArg(compiler->parent, CODE_CLOSURE, constant); + + // Emit arguments for each upvalue to know whether to capture a local or + // an upvalue. + for (int i = 0; i < compiler->fn->numUpvalues; i++) + { + emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0); + emitByte(compiler->parent, compiler->upvalues[i].index); + } + } + + // Pop this compiler off the stack. + compiler->parser->vm->compiler = compiler->parent; + + #if WREN_DEBUG_DUMP_COMPILED_CODE + wrenDumpCode(compiler->parser->vm, compiler->fn); + #endif + + return compiler->fn; +} + +// Grammar --------------------------------------------------------------------- + +typedef enum +{ + PREC_NONE, + PREC_LOWEST, + PREC_ASSIGNMENT, // = + PREC_CONDITIONAL, // ?: + PREC_LOGICAL_OR, // || + PREC_LOGICAL_AND, // && + PREC_EQUALITY, // == != + PREC_IS, // is + PREC_COMPARISON, // < > <= >= + PREC_BITWISE_OR, // | + PREC_BITWISE_XOR, // ^ + PREC_BITWISE_AND, // & + PREC_BITWISE_SHIFT, // << >> + PREC_RANGE, // .. ... + PREC_TERM, // + - + PREC_FACTOR, // * / % + PREC_UNARY, // unary - ! ~ + PREC_CALL, // . () [] + PREC_PRIMARY +} Precedence; + +typedef void (*GrammarFn)(Compiler*, bool canAssign); + +typedef void (*SignatureFn)(Compiler* compiler, Signature* signature); + +typedef struct +{ + GrammarFn prefix; + GrammarFn infix; + SignatureFn method; + Precedence precedence; + const char* name; +} GrammarRule; + +// Forward declarations since the grammar is recursive. +static GrammarRule* getRule(TokenType type); +static void expression(Compiler* compiler); +static void statement(Compiler* compiler); +static void definition(Compiler* compiler); +static void parsePrecedence(Compiler* compiler, Precedence precedence); + +// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF +// instruction with an offset that jumps to the current end of bytecode. +static void patchJump(Compiler* compiler, int offset) +{ + // -2 to adjust for the bytecode for the jump offset itself. + int jump = compiler->fn->code.count - offset - 2; + if (jump > MAX_JUMP) error(compiler, "Too much code to jump over."); + + compiler->fn->code.data[offset] = (jump >> 8) & 0xff; + compiler->fn->code.data[offset + 1] = jump & 0xff; +} + +// Parses a block body, after the initial "{" has been consumed. +// +// Returns true if it was a expression body, false if it was a statement body. +// (More precisely, returns true if a value was left on the stack. An empty +// block returns false.) +static bool finishBlock(Compiler* compiler) +{ + // Empty blocks do nothing. + if (match(compiler, TOKEN_RIGHT_BRACE)) return false; + + // If there's no line after the "{", it's a single-expression body. + if (!matchLine(compiler)) + { + expression(compiler); + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); + return true; + } + + // Empty blocks (with just a newline inside) do nothing. + if (match(compiler, TOKEN_RIGHT_BRACE)) return false; + + // Compile the definition list. + do + { + definition(compiler); + consumeLine(compiler, "Expect newline after statement."); + } + while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF); + + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block."); + return false; +} + +// Parses a method or function body, after the initial "{" has been consumed. +// +// If [Compiler->isInitializer] is `true`, this is the body of a constructor +// initializer. In that case, this adds the code to ensure it returns `this`. +static void finishBody(Compiler* compiler) +{ + bool isExpressionBody = finishBlock(compiler); + + if (compiler->isInitializer) + { + // If the initializer body evaluates to a value, discard it. + if (isExpressionBody) emitOp(compiler, CODE_POP); + + // The receiver is always stored in the first local slot. + emitOp(compiler, CODE_LOAD_LOCAL_0); + } + else if (!isExpressionBody) + { + // Implicitly return null in statement bodies. + emitOp(compiler, CODE_NULL); + } + + emitOp(compiler, CODE_RETURN); +} + +// The VM can only handle a certain number of parameters, so check that we +// haven't exceeded that and give a usable error. +static void validateNumParameters(Compiler* compiler, int numArgs) +{ + if (numArgs == MAX_PARAMETERS + 1) + { + // Only show an error at exactly max + 1 so that we can keep parsing the + // parameters and minimize cascaded errors. + error(compiler, "Methods cannot have more than %d parameters.", + MAX_PARAMETERS); + } +} + +// Parses the rest of a comma-separated parameter list after the opening +// delimeter. Updates `arity` in [signature] with the number of parameters. +static void finishParameterList(Compiler* compiler, Signature* signature) +{ + do + { + ignoreNewlines(compiler); + validateNumParameters(compiler, ++signature->arity); + + // Define a local variable in the method for the parameter. + declareNamedVariable(compiler); + } + while (match(compiler, TOKEN_COMMA)); +} + +// Gets the symbol for a method [name] with [length]. +static int methodSymbol(Compiler* compiler, const char* name, int length) +{ + return wrenSymbolTableEnsure(compiler->parser->vm, + &compiler->parser->vm->methodNames, name, length); +} + +// Appends characters to [name] (and updates [length]) for [numParams] "_" +// surrounded by [leftBracket] and [rightBracket]. +static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length, + int numParams, char leftBracket, char rightBracket) +{ + name[(*length)++] = leftBracket; + + // This function may be called with too many parameters. When that happens, + // a compile error has already been reported, but we need to make sure we + // don't overflow the string too, hence the MAX_PARAMETERS check. + for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++) + { + if (i > 0) name[(*length)++] = ','; + name[(*length)++] = '_'; + } + name[(*length)++] = rightBracket; +} + +// Fills [name] with the stringified version of [signature] and updates +// [length] to the resulting length. +static void signatureToString(Signature* signature, + char name[MAX_METHOD_SIGNATURE], int* length) +{ + *length = 0; + + // Build the full name from the signature. + memcpy(name + *length, signature->name, signature->length); + *length += signature->length; + + switch (signature->type) + { + case SIG_METHOD: + signatureParameterList(name, length, signature->arity, '(', ')'); + break; + + case SIG_GETTER: + // The signature is just the name. + break; + + case SIG_SETTER: + name[(*length)++] = '='; + signatureParameterList(name, length, 1, '(', ')'); + break; + + case SIG_SUBSCRIPT: + signatureParameterList(name, length, signature->arity, '[', ']'); + break; + + case SIG_SUBSCRIPT_SETTER: + signatureParameterList(name, length, signature->arity - 1, '[', ']'); + name[(*length)++] = '='; + signatureParameterList(name, length, 1, '(', ')'); + break; + + case SIG_INITIALIZER: + memcpy(name, "init ", 5); + memcpy(name + 5, signature->name, signature->length); + *length = 5 + signature->length; + signatureParameterList(name, length, signature->arity, '(', ')'); + break; + } + + name[*length] = '\0'; +} + +// Gets the symbol for a method with [signature]. +static int signatureSymbol(Compiler* compiler, Signature* signature) +{ + // Build the full name from the signature. + char name[MAX_METHOD_SIGNATURE]; + int length; + signatureToString(signature, name, &length); + + return methodSymbol(compiler, name, length); +} + +// Returns a signature with [type] whose name is from the last consumed token. +static Signature signatureFromToken(Compiler* compiler, SignatureType type) +{ + Signature signature; + + // Get the token for the method name. + Token* token = &compiler->parser->previous; + signature.name = token->start; + signature.length = token->length; + signature.type = type; + signature.arity = 0; + + if (signature.length > MAX_METHOD_NAME) + { + error(compiler, "Method names cannot be longer than %d characters.", + MAX_METHOD_NAME); + signature.length = MAX_METHOD_NAME; + } + + return signature; +} + +// Parses a comma-separated list of arguments. Modifies [signature] to include +// the arity of the argument list. +static void finishArgumentList(Compiler* compiler, Signature* signature) +{ + do + { + ignoreNewlines(compiler); + validateNumParameters(compiler, ++signature->arity); + expression(compiler); + } + while (match(compiler, TOKEN_COMMA)); + + // Allow a newline before the closing delimiter. + ignoreNewlines(compiler); +} + +// Compiles a method call with [signature] using [instruction]. +static void callSignature(Compiler* compiler, Code instruction, + Signature* signature) +{ + int symbol = signatureSymbol(compiler, signature); + emitShortArg(compiler, (Code)(instruction + signature->arity), symbol); + + if (instruction == CODE_SUPER_0) + { + // Super calls need to be statically bound to the class's superclass. This + // ensures we call the right method even when a method containing a super + // call is inherited by another subclass. + // + // We bind it at class definition time by storing a reference to the + // superclass in a constant. So, here, we create a slot in the constant + // table and store NULL in it. When the method is bound, we'll look up the + // superclass then and store it in the constant slot. + emitShort(compiler, addConstant(compiler, NULL_VAL)); + } +} + +// Compiles a method call with [numArgs] for a method with [name] with [length]. +static void callMethod(Compiler* compiler, int numArgs, const char* name, + int length) +{ + int symbol = methodSymbol(compiler, name, length); + emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol); +} + +// Compiles an (optional) argument list for a method call with [methodSignature] +// and then calls it. +static void methodCall(Compiler* compiler, Code instruction, + Signature* signature) +{ + // Make a new signature that contains the updated arity and type based on + // the arguments we find. + Signature called = { signature->name, signature->length, SIG_GETTER, 0 }; + + // Parse the argument list, if any. + if (match(compiler, TOKEN_LEFT_PAREN)) + { + called.type = SIG_METHOD; + + // Allow new line before an empty argument list + ignoreNewlines(compiler); + + // Allow empty an argument list. + if (peek(compiler) != TOKEN_RIGHT_PAREN) + { + finishArgumentList(compiler, &called); + } + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); + } + + // Parse the block argument, if any. + if (match(compiler, TOKEN_LEFT_BRACE)) + { + // Include the block argument in the arity. + called.type = SIG_METHOD; + called.arity++; + + Compiler fnCompiler; + initCompiler(&fnCompiler, compiler->parser, compiler, false); + + // Make a dummy signature to track the arity. + Signature fnSignature = { "", 0, SIG_METHOD, 0 }; + + // Parse the parameter list, if any. + if (match(compiler, TOKEN_PIPE)) + { + finishParameterList(&fnCompiler, &fnSignature); + consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters."); + } + + fnCompiler.fn->arity = fnSignature.arity; + + finishBody(&fnCompiler); + + // Name the function based on the method its passed to. + char blockName[MAX_METHOD_SIGNATURE + 15]; + int blockLength; + signatureToString(&called, blockName, &blockLength); + memmove(blockName + blockLength, " block argument", 16); + + endCompiler(&fnCompiler, blockName, blockLength + 15); + } + + // TODO: Allow Grace-style mixfix methods? + + // If this is a super() call for an initializer, make sure we got an actual + // argument list. + if (signature->type == SIG_INITIALIZER) + { + if (called.type != SIG_METHOD) + { + error(compiler, "A superclass constructor must have an argument list."); + } + + called.type = SIG_INITIALIZER; + } + + callSignature(compiler, instruction, &called); +} + +// Compiles a call whose name is the previously consumed token. This includes +// getters, method calls with arguments, and setter calls. +static void namedCall(Compiler* compiler, bool canAssign, Code instruction) +{ + // Get the token for the method name. + Signature signature = signatureFromToken(compiler, SIG_GETTER); + + if (canAssign && match(compiler, TOKEN_EQ)) + { + ignoreNewlines(compiler); + + // Build the setter signature. + signature.type = SIG_SETTER; + signature.arity = 1; + + // Compile the assigned value. + expression(compiler); + callSignature(compiler, instruction, &signature); + } + else + { + methodCall(compiler, instruction, &signature); + allowLineBeforeDot(compiler); + } +} + +// Emits the code to load [variable] onto the stack. +static void loadVariable(Compiler* compiler, Variable variable) +{ + switch (variable.scope) + { + case SCOPE_LOCAL: + loadLocal(compiler, variable.index); + break; + case SCOPE_UPVALUE: + emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index); + break; + case SCOPE_MODULE: + emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index); + break; + default: + UNREACHABLE(); + } +} + +// Loads the receiver of the currently enclosing method. Correctly handles +// functions defined inside methods. +static void loadThis(Compiler* compiler) +{ + loadVariable(compiler, resolveNonmodule(compiler, "this", 4)); +} + +// Pushes the value for a module-level variable implicitly imported from core. +static void loadCoreVariable(Compiler* compiler, const char* name) +{ + int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames, + name, strlen(name)); + ASSERT(symbol != -1, "Should have already defined core name."); + emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol); +} + +// A parenthesized expression. +static void grouping(Compiler* compiler, bool canAssign) +{ + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); +} + +// A list literal. +static void list(Compiler* compiler, bool canAssign) +{ + // Instantiate a new list. + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + + // Compile the list elements. Each one compiles to a ".add()" call. + do + { + ignoreNewlines(compiler); + + // Stop if we hit the end of the list. + if (peek(compiler) == TOKEN_RIGHT_BRACKET) break; + + // The element. + expression(compiler); + callMethod(compiler, 1, "addCore_(_)", 11); + } while (match(compiler, TOKEN_COMMA)); + + // Allow newlines before the closing ']'. + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); +} + +// A map literal. +static void map(Compiler* compiler, bool canAssign) +{ + // Instantiate a new map. + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + // Compile the map elements. Each one is compiled to just invoke the + // subscript setter on the map. + do + { + ignoreNewlines(compiler); + + // Stop if we hit the end of the map. + if (peek(compiler) == TOKEN_RIGHT_BRACE) break; + + // The key. + parsePrecedence(compiler, PREC_UNARY); + consume(compiler, TOKEN_COLON, "Expect ':' after map key."); + ignoreNewlines(compiler); + + // The value. + expression(compiler); + callMethod(compiler, 2, "addCore_(_,_)", 13); + } while (match(compiler, TOKEN_COMMA)); + + // Allow newlines before the closing '}'. + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries."); +} + +// Unary operators like `-foo`. +static void unaryOp(Compiler* compiler, bool canAssign) +{ + GrammarRule* rule = getRule(compiler->parser->previous.type); + + ignoreNewlines(compiler); + + // Compile the argument. + parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1)); + + // Call the operator method on the left-hand side. + callMethod(compiler, 0, rule->name, 1); +} + +static void boolean(Compiler* compiler, bool canAssign) +{ + emitOp(compiler, + compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE); +} + +// Walks the compiler chain to find the compiler for the nearest class +// enclosing this one. Returns NULL if not currently inside a class definition. +static Compiler* getEnclosingClassCompiler(Compiler* compiler) +{ + while (compiler != NULL) + { + if (compiler->enclosingClass != NULL) return compiler; + compiler = compiler->parent; + } + + return NULL; +} + +// Walks the compiler chain to find the nearest class enclosing this one. +// Returns NULL if not currently inside a class definition. +static ClassInfo* getEnclosingClass(Compiler* compiler) +{ + compiler = getEnclosingClassCompiler(compiler); + return compiler == NULL ? NULL : compiler->enclosingClass; +} + +static void field(Compiler* compiler, bool canAssign) +{ + // Initialize it with a fake value so we can keep parsing and minimize the + // number of cascaded errors. + int field = MAX_FIELDS; + + ClassInfo* enclosingClass = getEnclosingClass(compiler); + + if (enclosingClass == NULL) + { + error(compiler, "Cannot reference a field outside of a class definition."); + } + else if (enclosingClass->isForeign) + { + error(compiler, "Cannot define fields in a foreign class."); + } + else if (enclosingClass->inStatic) + { + error(compiler, "Cannot use an instance field in a static method."); + } + else + { + // Look up the field, or implicitly define it. + field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields, + compiler->parser->previous.start, + compiler->parser->previous.length); + + if (field >= MAX_FIELDS) + { + error(compiler, "A class can only have %d fields.", MAX_FIELDS); + } + } + + // If there's an "=" after a field name, it's an assignment. + bool isLoad = true; + if (canAssign && match(compiler, TOKEN_EQ)) + { + // Compile the right-hand side. + expression(compiler); + isLoad = false; + } + + // If we're directly inside a method, use a more optimal instruction. + if (compiler->parent != NULL && + compiler->parent->enclosingClass == enclosingClass) + { + emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS, + field); + } + else + { + loadThis(compiler); + emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field); + } + + allowLineBeforeDot(compiler); +} + +// Compiles a read or assignment to [variable]. +static void bareName(Compiler* compiler, bool canAssign, Variable variable) +{ + // If there's an "=" after a bare name, it's a variable assignment. + if (canAssign && match(compiler, TOKEN_EQ)) + { + // Compile the right-hand side. + expression(compiler); + + // Emit the store instruction. + switch (variable.scope) + { + case SCOPE_LOCAL: + emitByteArg(compiler, CODE_STORE_LOCAL, variable.index); + break; + case SCOPE_UPVALUE: + emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index); + break; + case SCOPE_MODULE: + emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index); + break; + default: + UNREACHABLE(); + } + return; + } + + // Emit the load instruction. + loadVariable(compiler, variable); + + allowLineBeforeDot(compiler); +} + +static void staticField(Compiler* compiler, bool canAssign) +{ + Compiler* classCompiler = getEnclosingClassCompiler(compiler); + if (classCompiler == NULL) + { + error(compiler, "Cannot use a static field outside of a class definition."); + return; + } + + // Look up the name in the scope chain. + Token* token = &compiler->parser->previous; + + // If this is the first time we've seen this static field, implicitly + // define it as a variable in the scope surrounding the class definition. + if (resolveLocal(classCompiler, token->start, token->length) == -1) + { + int symbol = declareVariable(classCompiler, NULL); + + // Implicitly initialize it to null. + emitOp(classCompiler, CODE_NULL); + defineVariable(classCompiler, symbol); + } + + // It definitely exists now, so resolve it properly. This is different from + // the above resolveLocal() call because we may have already closed over it + // as an upvalue. + Variable variable = resolveName(compiler, token->start, token->length); + bareName(compiler, canAssign, variable); +} + +// Compiles a variable name or method call with an implicit receiver. +static void name(Compiler* compiler, bool canAssign) +{ + // Look for the name in the scope chain up to the nearest enclosing method. + Token* token = &compiler->parser->previous; + + Variable variable = resolveNonmodule(compiler, token->start, token->length); + if (variable.index != -1) + { + bareName(compiler, canAssign, variable); + return; + } + + // TODO: The fact that we return above here if the variable is known and parse + // an optional argument list below if not means that the grammar is not + // context-free. A line of code in a method like "someName(foo)" is a parse + // error if "someName" is a defined variable in the surrounding scope and not + // if it isn't. Fix this. One option is to have "someName(foo)" always + // resolve to a self-call if there is an argument list, but that makes + // getters a little confusing. + + // If we're inside a method and the name is lowercase, treat it as a method + // on this. + if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL) + { + loadThis(compiler); + namedCall(compiler, canAssign, CODE_CALL_0); + return; + } + + // Otherwise, look for a module-level variable with the name. + variable.scope = SCOPE_MODULE; + variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames, + token->start, token->length); + if (variable.index == -1) + { + // Implicitly define a module-level variable in + // the hopes that we get a real definition later. + variable.index = wrenDeclareVariable(compiler->parser->vm, + compiler->parser->module, + token->start, token->length, + token->line); + + if (variable.index == -2) + { + error(compiler, "Too many module variables defined."); + } + } + + bareName(compiler, canAssign, variable); +} + +static void null(Compiler* compiler, bool canAssign) +{ + emitOp(compiler, CODE_NULL); +} + +// A number or string literal. +static void literal(Compiler* compiler, bool canAssign) +{ + emitConstant(compiler, compiler->parser->previous.value); +} + +// A string literal that contains interpolated expressions. +// +// Interpolation is syntactic sugar for calling ".join()" on a list. So the +// string: +// +// "a %(b + c) d" +// +// is compiled roughly like: +// +// ["a ", b + c, " d"].join() +static void stringInterpolation(Compiler* compiler, bool canAssign) +{ + // Instantiate a new list. + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + + do + { + // The opening string part. + literal(compiler, false); + callMethod(compiler, 1, "addCore_(_)", 11); + + // The interpolated expression. + ignoreNewlines(compiler); + expression(compiler); + callMethod(compiler, 1, "addCore_(_)", 11); + + ignoreNewlines(compiler); + } while (match(compiler, TOKEN_INTERPOLATION)); + + // The trailing string part. + consume(compiler, TOKEN_STRING, "Expect end of string interpolation."); + literal(compiler, false); + callMethod(compiler, 1, "addCore_(_)", 11); + + // The list of interpolated parts. + callMethod(compiler, 0, "join()", 6); +} + +static void super_(Compiler* compiler, bool canAssign) +{ + ClassInfo* enclosingClass = getEnclosingClass(compiler); + if (enclosingClass == NULL) + { + error(compiler, "Cannot use 'super' outside of a method."); + } + + loadThis(compiler); + + // TODO: Super operator calls. + // TODO: There's no syntax for invoking a superclass constructor with a + // different name from the enclosing one. Figure that out. + + // See if it's a named super call, or an unnamed one. + if (match(compiler, TOKEN_DOT)) + { + // Compile the superclass call. + consume(compiler, TOKEN_NAME, "Expect method name after 'super.'."); + namedCall(compiler, canAssign, CODE_SUPER_0); + } + else if (enclosingClass != NULL) + { + // No explicit name, so use the name of the enclosing method. Make sure we + // check that enclosingClass isn't NULL first. We've already reported the + // error, but we don't want to crash here. + methodCall(compiler, CODE_SUPER_0, enclosingClass->signature); + } +} + +static void this_(Compiler* compiler, bool canAssign) +{ + if (getEnclosingClass(compiler) == NULL) + { + error(compiler, "Cannot use 'this' outside of a method."); + return; + } + + loadThis(compiler); +} + +// Subscript or "array indexing" operator like `foo[bar]`. +static void subscript(Compiler* compiler, bool canAssign) +{ + Signature signature = { "", 0, SIG_SUBSCRIPT, 0 }; + + // Parse the argument list. + finishArgumentList(compiler, &signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments."); + + allowLineBeforeDot(compiler); + + if (canAssign && match(compiler, TOKEN_EQ)) + { + signature.type = SIG_SUBSCRIPT_SETTER; + + // Compile the assigned value. + validateNumParameters(compiler, ++signature.arity); + expression(compiler); + } + + callSignature(compiler, CODE_CALL_0, &signature); +} + +static void call(Compiler* compiler, bool canAssign) +{ + ignoreNewlines(compiler); + consume(compiler, TOKEN_NAME, "Expect method name after '.'."); + namedCall(compiler, canAssign, CODE_CALL_0); +} + +static void and_(Compiler* compiler, bool canAssign) +{ + ignoreNewlines(compiler); + + // Skip the right argument if the left is false. + int jump = emitJump(compiler, CODE_AND); + parsePrecedence(compiler, PREC_LOGICAL_AND); + patchJump(compiler, jump); +} + +static void or_(Compiler* compiler, bool canAssign) +{ + ignoreNewlines(compiler); + + // Skip the right argument if the left is true. + int jump = emitJump(compiler, CODE_OR); + parsePrecedence(compiler, PREC_LOGICAL_OR); + patchJump(compiler, jump); +} + +static void conditional(Compiler* compiler, bool canAssign) +{ + // Ignore newline after '?'. + ignoreNewlines(compiler); + + // Jump to the else branch if the condition is false. + int ifJump = emitJump(compiler, CODE_JUMP_IF); + + // Compile the then branch. + parsePrecedence(compiler, PREC_CONDITIONAL); + + consume(compiler, TOKEN_COLON, + "Expect ':' after then branch of conditional operator."); + ignoreNewlines(compiler); + + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); + + // Compile the else branch. + patchJump(compiler, ifJump); + + parsePrecedence(compiler, PREC_ASSIGNMENT); + + // Patch the jump over the else. + patchJump(compiler, elseJump); +} + +void infixOp(Compiler* compiler, bool canAssign) +{ + GrammarRule* rule = getRule(compiler->parser->previous.type); + + // An infix operator cannot end an expression. + ignoreNewlines(compiler); + + // Compile the right-hand side. + parsePrecedence(compiler, (Precedence)(rule->precedence + 1)); + + // Call the operator method on the left-hand side. + Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 }; + callSignature(compiler, CODE_CALL_0, &signature); +} + +// Compiles a method signature for an infix operator. +void infixSignature(Compiler* compiler, Signature* signature) +{ + // Add the RHS parameter. + signature->type = SIG_METHOD; + signature->arity = 1; + + // Parse the parameter name. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name."); + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); +} + +// Compiles a method signature for an unary operator (i.e. "!"). +void unarySignature(Compiler* compiler, Signature* signature) +{ + // Do nothing. The name is already complete. + signature->type = SIG_GETTER; +} + +// Compiles a method signature for an operator that can either be unary or +// infix (i.e. "-"). +void mixedSignature(Compiler* compiler, Signature* signature) +{ + signature->type = SIG_GETTER; + + // If there is a parameter, it's an infix operator, otherwise it's unary. + if (match(compiler, TOKEN_LEFT_PAREN)) + { + // Add the RHS parameter. + signature->type = SIG_METHOD; + signature->arity = 1; + + // Parse the parameter name. + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); + } +} + +// Compiles an optional setter parameter in a method [signature]. +// +// Returns `true` if it was a setter. +static bool maybeSetter(Compiler* compiler, Signature* signature) +{ + // See if it's a setter. + if (!match(compiler, TOKEN_EQ)) return false; + + // It's a setter. + if (signature->type == SIG_SUBSCRIPT) + { + signature->type = SIG_SUBSCRIPT_SETTER; + } + else + { + signature->type = SIG_SETTER; + } + + // Parse the value parameter. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='."); + declareNamedVariable(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name."); + + signature->arity++; + + return true; +} + +// Compiles a method signature for a subscript operator. +void subscriptSignature(Compiler* compiler, Signature* signature) +{ + signature->type = SIG_SUBSCRIPT; + + // The signature currently has "[" as its name since that was the token that + // matched it. Clear that out. + signature->length = 0; + + // Parse the parameters inside the subscript. + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters."); + + maybeSetter(compiler, signature); +} + +// Parses an optional parenthesized parameter list. Updates `type` and `arity` +// in [signature] to match what was parsed. +static void parameterList(Compiler* compiler, Signature* signature) +{ + // The parameter list is optional. + if (!match(compiler, TOKEN_LEFT_PAREN)) return; + + signature->type = SIG_METHOD; + + // Allow new line before an empty argument list + ignoreNewlines(compiler); + + // Allow an empty parameter list. + if (match(compiler, TOKEN_RIGHT_PAREN)) return; + + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); +} + +// Compiles a method signature for a named method or setter. +void namedSignature(Compiler* compiler, Signature* signature) +{ + signature->type = SIG_GETTER; + + // If it's a setter, it can't also have a parameter list. + if (maybeSetter(compiler, signature)) return; + + // Regular named method with an optional parameter list. + parameterList(compiler, signature); +} + +// Compiles a method signature for a constructor. +void constructorSignature(Compiler* compiler, Signature* signature) +{ + consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'."); + + // Capture the name. + *signature = signatureFromToken(compiler, SIG_INITIALIZER); + + if (match(compiler, TOKEN_EQ)) + { + error(compiler, "A constructor cannot be a setter."); + } + + if (!match(compiler, TOKEN_LEFT_PAREN)) + { + error(compiler, "A constructor cannot be a getter."); + return; + } + + // Allow an empty parameter list. + if (match(compiler, TOKEN_RIGHT_PAREN)) return; + + finishParameterList(compiler, signature); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters."); +} + +// This table defines all of the parsing rules for the prefix and infix +// expressions in the grammar. Expressions are parsed using a Pratt parser. +// +// See: http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ +#define UNUSED { NULL, NULL, NULL, PREC_NONE, NULL } +#define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL } +#define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL } +#define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name } +#define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name } +#define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name } + +GrammarRule rules[] = +{ + /* TOKEN_LEFT_PAREN */ PREFIX(grouping), + /* TOKEN_RIGHT_PAREN */ UNUSED, + /* TOKEN_LEFT_BRACKET */ { list, subscript, subscriptSignature, PREC_CALL, NULL }, + /* TOKEN_RIGHT_BRACKET */ UNUSED, + /* TOKEN_LEFT_BRACE */ PREFIX(map), + /* TOKEN_RIGHT_BRACE */ UNUSED, + /* TOKEN_COLON */ UNUSED, + /* TOKEN_DOT */ INFIX(PREC_CALL, call), + /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."), + /* TOKEN_DOTDOTDOT */ INFIX_OPERATOR(PREC_RANGE, "..."), + /* TOKEN_COMMA */ UNUSED, + /* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"), + /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"), + /* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"), + /* TOKEN_HASH */ UNUSED, + /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"), + /* TOKEN_MINUS */ OPERATOR("-"), + /* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"), + /* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"), + /* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"), + /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_), + /* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"), + /* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"), + /* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_), + /* TOKEN_BANG */ PREFIX_OPERATOR("!"), + /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), + /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), + /* TOKEN_EQ */ UNUSED, + /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), + /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), + /* TOKEN_LTEQ */ INFIX_OPERATOR(PREC_COMPARISON, "<="), + /* TOKEN_GTEQ */ INFIX_OPERATOR(PREC_COMPARISON, ">="), + /* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "=="), + /* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="), + /* TOKEN_BREAK */ UNUSED, + /* TOKEN_CONTINUE */ UNUSED, + /* TOKEN_CLASS */ UNUSED, + /* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL }, + /* TOKEN_ELSE */ UNUSED, + /* TOKEN_FALSE */ PREFIX(boolean), + /* TOKEN_FOR */ UNUSED, + /* TOKEN_FOREIGN */ UNUSED, + /* TOKEN_IF */ UNUSED, + /* TOKEN_IMPORT */ UNUSED, + /* TOKEN_AS */ UNUSED, + /* TOKEN_IN */ UNUSED, + /* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"), + /* TOKEN_NULL */ PREFIX(null), + /* TOKEN_RETURN */ UNUSED, + /* TOKEN_STATIC */ UNUSED, + /* TOKEN_SUPER */ PREFIX(super_), + /* TOKEN_THIS */ PREFIX(this_), + /* TOKEN_TRUE */ PREFIX(boolean), + /* TOKEN_VAR */ UNUSED, + /* TOKEN_WHILE */ UNUSED, + /* TOKEN_FIELD */ PREFIX(field), + /* TOKEN_STATIC_FIELD */ PREFIX(staticField), + /* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL }, + /* TOKEN_NUMBER */ PREFIX(literal), + /* TOKEN_STRING */ PREFIX(literal), + /* TOKEN_INTERPOLATION */ PREFIX(stringInterpolation), + /* TOKEN_LINE */ UNUSED, + /* TOKEN_ERROR */ UNUSED, + /* TOKEN_EOF */ UNUSED +}; + +// Gets the [GrammarRule] associated with tokens of [type]. +static GrammarRule* getRule(TokenType type) +{ + return &rules[type]; +} + +// The main entrypoint for the top-down operator precedence parser. +void parsePrecedence(Compiler* compiler, Precedence precedence) +{ + nextToken(compiler->parser); + GrammarFn prefix = rules[compiler->parser->previous.type].prefix; + + if (prefix == NULL) + { + error(compiler, "Expected expression."); + return; + } + + // Track if the precendence of the surrounding expression is low enough to + // allow an assignment inside this one. We can't compile an assignment like + // a normal expression because it requires us to handle the LHS specially -- + // it needs to be an lvalue, not an rvalue. So, for each of the kinds of + // expressions that are valid lvalues -- names, subscripts, fields, etc. -- + // we pass in whether or not it appears in a context loose enough to allow + // "=". If so, it will parse the "=" itself and handle it appropriately. + bool canAssign = precedence <= PREC_CONDITIONAL; + prefix(compiler, canAssign); + + while (precedence <= rules[compiler->parser->current.type].precedence) + { + nextToken(compiler->parser); + GrammarFn infix = rules[compiler->parser->previous.type].infix; + infix(compiler, canAssign); + } +} + +// Parses an expression. Unlike statements, expressions leave a resulting value +// on the stack. +void expression(Compiler* compiler) +{ + parsePrecedence(compiler, PREC_LOWEST); +} + +// Returns the number of bytes for the arguments to the instruction +// at [ip] in [fn]'s bytecode. +static int getByteCountForArguments(const uint8_t* bytecode, + const Value* constants, int ip) +{ + Code instruction = (Code)bytecode[ip]; + switch (instruction) + { + case CODE_NULL: + case CODE_FALSE: + case CODE_TRUE: + case CODE_POP: + case CODE_CLOSE_UPVALUE: + case CODE_RETURN: + case CODE_END: + case CODE_LOAD_LOCAL_0: + case CODE_LOAD_LOCAL_1: + case CODE_LOAD_LOCAL_2: + case CODE_LOAD_LOCAL_3: + case CODE_LOAD_LOCAL_4: + case CODE_LOAD_LOCAL_5: + case CODE_LOAD_LOCAL_6: + case CODE_LOAD_LOCAL_7: + case CODE_LOAD_LOCAL_8: + case CODE_CONSTRUCT: + case CODE_FOREIGN_CONSTRUCT: + case CODE_FOREIGN_CLASS: + case CODE_END_MODULE: + case CODE_END_CLASS: + return 0; + + case CODE_LOAD_LOCAL: + case CODE_STORE_LOCAL: + case CODE_LOAD_UPVALUE: + case CODE_STORE_UPVALUE: + case CODE_LOAD_FIELD_THIS: + case CODE_STORE_FIELD_THIS: + case CODE_LOAD_FIELD: + case CODE_STORE_FIELD: + case CODE_CLASS: + return 1; + + case CODE_CONSTANT: + case CODE_LOAD_MODULE_VAR: + case CODE_STORE_MODULE_VAR: + 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: + case CODE_JUMP: + case CODE_LOOP: + case CODE_JUMP_IF: + case CODE_AND: + case CODE_OR: + case CODE_METHOD_INSTANCE: + case CODE_METHOD_STATIC: + case CODE_IMPORT_MODULE: + case CODE_IMPORT_VARIABLE: + return 2; + + 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: + return 4; + + case CODE_CLOSURE: + { + int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2]; + ObjFn* loadedFn = AS_FN(constants[constant]); + + // There are two bytes for the constant, then two for each upvalue. + return 2 + (loadedFn->numUpvalues * 2); + } + } + + UNREACHABLE(); + return 0; +} + +// Marks the beginning of a loop. Keeps track of the current instruction so we +// know what to loop back to at the end of the body. +static void startLoop(Compiler* compiler, Loop* loop) +{ + loop->enclosing = compiler->loop; + loop->start = compiler->fn->code.count - 1; + loop->scopeDepth = compiler->scopeDepth; + compiler->loop = loop; +} + +// Emits the [CODE_JUMP_IF] instruction used to test the loop condition and +// potentially exit the loop. Keeps track of the instruction so we can patch it +// later once we know where the end of the body is. +static void testExitLoop(Compiler* compiler) +{ + compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF); +} + +// Compiles the body of the loop and tracks its extent so that contained "break" +// statements can be handled correctly. +static void loopBody(Compiler* compiler) +{ + compiler->loop->body = compiler->fn->code.count; + statement(compiler); +} + +// Ends the current innermost loop. Patches up all jumps and breaks now that +// we know where the end of the loop is. +static void endLoop(Compiler* compiler) +{ + // We don't check for overflow here since the forward jump over the loop body + // will report an error for the same problem. + int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; + emitShortArg(compiler, CODE_LOOP, loopOffset); + + patchJump(compiler, compiler->loop->exitJump); + + // Find any break placeholder instructions (which will be CODE_END in the + // bytecode) and replace them with real jumps. + int i = compiler->loop->body; + while (i < compiler->fn->code.count) + { + if (compiler->fn->code.data[i] == CODE_END) + { + compiler->fn->code.data[i] = CODE_JUMP; + patchJump(compiler, i + 1); + i += 3; + } + else + { + // Skip this instruction and its arguments. + i += 1 + getByteCountForArguments(compiler->fn->code.data, + compiler->fn->constants.data, i); + } + } + + compiler->loop = compiler->loop->enclosing; +} + +static void forStatement(Compiler* compiler) +{ + // A for statement like: + // + // for (i in sequence.expression) { + // System.print(i) + // } + // + // Is compiled to bytecode almost as if the source looked like this: + // + // { + // var seq_ = sequence.expression + // var iter_ + // while (iter_ = seq_.iterate(iter_)) { + // var i = seq_.iteratorValue(iter_) + // System.print(i) + // } + // } + // + // It's not exactly this, because the synthetic variables `seq_` and `iter_` + // actually get names that aren't valid Wren identfiers, but that's the basic + // idea. + // + // The important parts are: + // - The sequence expression is only evaluated once. + // - The .iterate() method is used to advance the iterator and determine if + // it should exit the loop. + // - The .iteratorValue() method is used to get the value at the current + // iterator position. + + // Create a scope for the hidden local variables used for the iterator. + pushScope(compiler); + + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); + consume(compiler, TOKEN_NAME, "Expect for loop variable name."); + + // Remember the name of the loop variable. + const char* name = compiler->parser->previous.start; + int length = compiler->parser->previous.length; + + consume(compiler, TOKEN_IN, "Expect 'in' after loop variable."); + ignoreNewlines(compiler); + + // Evaluate the sequence expression and store it in a hidden local variable. + // The space in the variable name ensures it won't collide with a user-defined + // variable. + expression(compiler); + + // Verify that there is space to hidden local variables. + // Note that we expect only two addLocal calls next to each other in the + // following code. + if (compiler->numLocals + 2 > MAX_LOCALS) + { + error(compiler, "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)", + MAX_LOCALS); + return; + } + int seqSlot = addLocal(compiler, "seq ", 4); + + // Create another hidden local for the iterator object. + null(compiler, false); + int iterSlot = addLocal(compiler, "iter ", 5); + + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); + + Loop loop; + startLoop(compiler, &loop); + + // Advance the iterator by calling the ".iterate" method on the sequence. + loadLocal(compiler, seqSlot); + loadLocal(compiler, iterSlot); + + // Update and test the iterator. + callMethod(compiler, 1, "iterate(_)", 10); + emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot); + testExitLoop(compiler); + + // Get the current value in the sequence by calling ".iteratorValue". + loadLocal(compiler, seqSlot); + loadLocal(compiler, iterSlot); + callMethod(compiler, 1, "iteratorValue(_)", 16); + + // Bind the loop variable in its own scope. This ensures we get a fresh + // variable each iteration so that closures for it don't all see the same one. + pushScope(compiler); + addLocal(compiler, name, length); + + loopBody(compiler); + + // Loop variable. + popScope(compiler); + + endLoop(compiler); + + // Hidden variables. + popScope(compiler); +} + +static void ifStatement(Compiler* compiler) +{ + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'."); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); + + // Jump to the else branch if the condition is false. + int ifJump = emitJump(compiler, CODE_JUMP_IF); + + // Compile the then branch. + statement(compiler); + + // Compile the else branch if there is one. + if (match(compiler, TOKEN_ELSE)) + { + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); + patchJump(compiler, ifJump); + + statement(compiler); + + // Patch the jump over the else. + patchJump(compiler, elseJump); + } + else + { + patchJump(compiler, ifJump); + } +} + +static void whileStatement(Compiler* compiler) +{ + Loop loop; + startLoop(compiler, &loop); + + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); + + testExitLoop(compiler); + loopBody(compiler); + endLoop(compiler); +} + +// Compiles a simple statement. These can only appear at the top-level or +// within curly blocks. Simple statements exclude variable binding statements +// like "var" and "class" which are not allowed directly in places like the +// branches of an "if" statement. +// +// Unlike expressions, statements do not leave a value on the stack. +void statement(Compiler* compiler) +{ + if (match(compiler, TOKEN_BREAK)) + { + if (compiler->loop == NULL) + { + error(compiler, "Cannot use 'break' outside of a loop."); + return; + } + + // Since we will be jumping out of the scope, make sure any locals in it + // are discarded first. + discardLocals(compiler, compiler->loop->scopeDepth + 1); + + // Emit a placeholder instruction for the jump to the end of the body. When + // we're done compiling the loop body and know where the end is, we'll + // replace these with `CODE_JUMP` instructions with appropriate offsets. + // We use `CODE_END` here because that can't occur in the middle of + // bytecode. + emitJump(compiler, CODE_END); + } + else if (match(compiler, TOKEN_CONTINUE)) + { + if (compiler->loop == NULL) + { + error(compiler, "Cannot use 'continue' outside of a loop."); + return; + } + + // Since we will be jumping out of the scope, make sure any locals in it + // are discarded first. + discardLocals(compiler, compiler->loop->scopeDepth + 1); + + // emit a jump back to the top of the loop + int loopOffset = compiler->fn->code.count - compiler->loop->start + 2; + emitShortArg(compiler, CODE_LOOP, loopOffset); + } + else if (match(compiler, TOKEN_FOR)) + { + forStatement(compiler); + } + else if (match(compiler, TOKEN_IF)) + { + ifStatement(compiler); + } + else if (match(compiler, TOKEN_RETURN)) + { + // Compile the return value. + if (peek(compiler) == TOKEN_LINE) + { + // If there's no expression after return, initializers should + // return 'this' and regular methods should return null + Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL; + emitOp(compiler, result); + } + else + { + if (compiler->isInitializer) + { + error(compiler, "A constructor cannot return a value."); + } + + expression(compiler); + } + + emitOp(compiler, CODE_RETURN); + } + else if (match(compiler, TOKEN_WHILE)) + { + whileStatement(compiler); + } + else if (match(compiler, TOKEN_LEFT_BRACE)) + { + // Block statement. + pushScope(compiler); + if (finishBlock(compiler)) + { + // Block was an expression, so discard it. + emitOp(compiler, CODE_POP); + } + popScope(compiler); + } + else + { + // Expression statement. + expression(compiler); + emitOp(compiler, CODE_POP); + } +} + +// Creates a matching constructor method for an initializer with [signature] +// and [initializerSymbol]. +// +// Construction is a two-stage process in Wren that involves two separate +// methods. There is a static method that allocates a new instance of the class. +// It then invokes an initializer method on the new instance, forwarding all of +// the constructor arguments to it. +// +// The allocator method always has a fixed implementation: +// +// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it. +// CODE_CALL - Invoke the initializer on the new instance. +// +// This creates that method and calls the initializer with [initializerSymbol]. +static void createConstructor(Compiler* compiler, Signature* signature, + int initializerSymbol) +{ + Compiler methodCompiler; + initCompiler(&methodCompiler, compiler->parser, compiler, true); + + // Allocate the instance. + emitOp(&methodCompiler, compiler->enclosingClass->isForeign + ? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT); + + // Run its initializer. + emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity), + initializerSymbol); + + // Return the instance. + emitOp(&methodCompiler, CODE_RETURN); + + endCompiler(&methodCompiler, "", 0); +} + +// Loads the enclosing class onto the stack and then binds the function already +// on the stack as a method on that class. +static void defineMethod(Compiler* compiler, Variable classVariable, + bool isStatic, int methodSymbol) +{ + // Load the class. We have to do this for each method because we can't + // keep the class on top of the stack. If there are static fields, they + // will be locals above the initial variable slot for the class on the + // stack. To skip past those, we just load the class each time right before + // defining a method. + loadVariable(compiler, classVariable); + + // Define the method. + Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE; + emitShortArg(compiler, instruction, methodSymbol); +} + +// Declares a method in the enclosing class with [signature]. +// +// Reports an error if a method with that signature is already declared. +// Returns the symbol for the method. +static int declareMethod(Compiler* compiler, Signature* signature, + const char* name, int length) +{ + int symbol = signatureSymbol(compiler, signature); + + // See if the class has already declared method with this signature. + ClassInfo* classInfo = compiler->enclosingClass; + IntBuffer* methods = classInfo->inStatic + ? &classInfo->staticMethods : &classInfo->methods; + for (int i = 0; i < methods->count; i++) + { + if (methods->data[i] == symbol) + { + const char* staticPrefix = classInfo->inStatic ? "static " : ""; + error(compiler, "Class %s already defines a %smethod '%s'.", + &compiler->enclosingClass->name->value, staticPrefix, name); + break; + } + } + + wrenIntBufferWrite(compiler->parser->vm, methods, symbol); + return symbol; +} + +static Value consumeLiteral(Compiler* compiler, const char* message) +{ + if(match(compiler, TOKEN_FALSE)) return FALSE_VAL; + if(match(compiler, TOKEN_TRUE)) return TRUE_VAL; + if(match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value; + if(match(compiler, TOKEN_STRING)) return compiler->parser->previous.value; + if(match(compiler, TOKEN_NAME)) return compiler->parser->previous.value; + + error(compiler, message); + nextToken(compiler->parser); + return NULL_VAL; +} + +static bool matchAttribute(Compiler* compiler) { + + if(match(compiler, TOKEN_HASH)) + { + compiler->numAttributes++; + bool runtimeAccess = match(compiler, TOKEN_BANG); + if(match(compiler, TOKEN_NAME)) + { + Value group = compiler->parser->previous.value; + TokenType ahead = peek(compiler); + if(ahead == TOKEN_EQ || ahead == TOKEN_LINE) + { + Value key = group; + Value value = NULL_VAL; + if(match(compiler, TOKEN_EQ)) + { + value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); + } + if(runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value); + } + else if(match(compiler, TOKEN_LEFT_PAREN)) + { + ignoreNewlines(compiler); + if(match(compiler, TOKEN_RIGHT_PAREN)) + { + error(compiler, "Expected attributes in group, group cannot be empty."); + } + else + { + while(peek(compiler) != TOKEN_RIGHT_PAREN) + { + consume(compiler, TOKEN_NAME, "Expect name for attribute key."); + Value key = compiler->parser->previous.value; + Value value = NULL_VAL; + if(match(compiler, TOKEN_EQ)) + { + value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value."); + } + if(runtimeAccess) addToAttributeGroup(compiler, group, key, value); + ignoreNewlines(compiler); + if(!match(compiler, TOKEN_COMMA)) break; + ignoreNewlines(compiler); + } + + ignoreNewlines(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, + "Expected ')' after grouped attributes."); + } + } + else + { + error(compiler, "Expect an equal, newline or grouping after an attribute key."); + } + } + else + { + error(compiler, "Expect an attribute definition after #."); + } + + consumeLine(compiler, "Expect newline after attribute."); + return true; + } + + return false; +} + +// Compiles a method definition inside a class body. +// +// Returns `true` if it compiled successfully, or `false` if the method couldn't +// be parsed. +static bool method(Compiler* compiler, Variable classVariable) +{ + // Parse any attributes before the method and store them + if(matchAttribute(compiler)) { + return method(compiler, classVariable); + } + + // TODO: What about foreign constructors? + bool isForeign = match(compiler, TOKEN_FOREIGN); + bool isStatic = match(compiler, TOKEN_STATIC); + compiler->enclosingClass->inStatic = isStatic; + + SignatureFn signatureFn = rules[compiler->parser->current.type].method; + nextToken(compiler->parser); + + if (signatureFn == NULL) + { + error(compiler, "Expect method definition."); + return false; + } + + // Build the method signature. + Signature signature = signatureFromToken(compiler, SIG_GETTER); + compiler->enclosingClass->signature = &signature; + + Compiler methodCompiler; + initCompiler(&methodCompiler, compiler->parser, compiler, true); + + // Compile the method signature. + signatureFn(&methodCompiler, &signature); + + methodCompiler.isInitializer = signature.type == SIG_INITIALIZER; + + if (isStatic && signature.type == SIG_INITIALIZER) + { + error(compiler, "A constructor cannot be static."); + } + + // Include the full signature in debug messages in stack traces. + char fullSignature[MAX_METHOD_SIGNATURE]; + int length; + signatureToString(&signature, fullSignature, &length); + + // Copy any attributes the compiler collected into the enclosing class + copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length); + + // Check for duplicate methods. Doesn't matter that it's already been + // defined, error will discard bytecode anyway. + // Check if the method table already contains this symbol + int methodSymbol = declareMethod(compiler, &signature, fullSignature, length); + + if (isForeign) + { + // Define a constant for the signature. + emitConstant(compiler, wrenNewStringLength(compiler->parser->vm, + fullSignature, length)); + + // We don't need the function we started compiling in the parameter list + // any more. + methodCompiler.parser->vm->compiler = methodCompiler.parent; + } + else + { + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); + finishBody(&methodCompiler); + endCompiler(&methodCompiler, fullSignature, length); + } + + // Define the method. For a constructor, this defines the instance + // initializer method. + defineMethod(compiler, classVariable, isStatic, methodSymbol); + + if (signature.type == SIG_INITIALIZER) + { + // Also define a matching constructor method on the metaclass. + signature.type = SIG_METHOD; + int constructorSymbol = signatureSymbol(compiler, &signature); + + createConstructor(compiler, &signature, methodSymbol); + defineMethod(compiler, classVariable, true, constructorSymbol); + } + + return true; +} + +// Compiles a class definition. Assumes the "class" token has already been +// consumed (along with a possibly preceding "foreign" token). +static void classDefinition(Compiler* compiler, bool isForeign) +{ + // Create a variable to store the class in. + Variable classVariable; + classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL; + classVariable.index = declareNamedVariable(compiler); + + // Create shared class name value + Value classNameString = wrenNewStringLength(compiler->parser->vm, + compiler->parser->previous.start, compiler->parser->previous.length); + + // Create class name string to track method duplicates + ObjString* className = AS_STRING(classNameString); + + // Make a string constant for the name. + emitConstant(compiler, classNameString); + + // Load the superclass (if there is one). + if (match(compiler, TOKEN_IS)) + { + parsePrecedence(compiler, PREC_CALL); + } + else + { + // Implicitly inherit from Object. + loadCoreVariable(compiler, "Object"); + } + + // Store a placeholder for the number of fields argument. We don't know the + // count until we've compiled all the methods to see which fields are used. + int numFieldsInstruction = -1; + if (isForeign) + { + emitOp(compiler, CODE_FOREIGN_CLASS); + } + else + { + numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); + } + + // Store it in its name. + defineVariable(compiler, classVariable.index); + + // Push a local variable scope. Static fields in a class body are hoisted out + // into local variables declared in this scope. Methods that use them will + // have upvalues referencing them. + pushScope(compiler); + + ClassInfo classInfo; + classInfo.isForeign = isForeign; + classInfo.name = className; + + // Allocate attribute maps if necessary. + // A method will allocate the methods one if needed + classInfo.classAttributes = compiler->attributes->count > 0 + ? wrenNewMap(compiler->parser->vm) + : NULL; + classInfo.methodAttributes = NULL; + // Copy any existing attributes into the class + copyAttributes(compiler, classInfo.classAttributes); + + // Set up a symbol table for the class's fields. We'll initially compile + // them to slots starting at zero. When the method is bound to the class, the + // bytecode will be adjusted by [wrenBindMethod] to take inherited fields + // into account. + wrenSymbolTableInit(&classInfo.fields); + + // Set up symbol buffers to track duplicate static and instance methods. + wrenIntBufferInit(&classInfo.methods); + wrenIntBufferInit(&classInfo.staticMethods); + compiler->enclosingClass = &classInfo; + + // Compile the method definitions. + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration."); + matchLine(compiler); + + while (!match(compiler, TOKEN_RIGHT_BRACE)) + { + if (!method(compiler, classVariable)) break; + + // Don't require a newline after the last definition. + if (match(compiler, TOKEN_RIGHT_BRACE)) break; + + consumeLine(compiler, "Expect newline after definition in class."); + } + + // If any attributes are present, + // instantiate a ClassAttributes instance for the class + // and send it over to CODE_END_CLASS + bool hasAttr = classInfo.classAttributes != NULL || + classInfo.methodAttributes != NULL; + if(hasAttr) { + emitClassAttributes(compiler, &classInfo); + loadVariable(compiler, classVariable); + // At the moment, we don't have other uses for CODE_END_CLASS, + // so we put it inside this condition. Later, we can always + // emit it and use it as needed. + emitOp(compiler, CODE_END_CLASS); + } + + // Update the class with the number of fields. + if (!isForeign) + { + compiler->fn->code.data[numFieldsInstruction] = + (uint8_t)classInfo.fields.count; + } + + // Clear symbol tables for tracking field and method names. + wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields); + wrenIntBufferClear(compiler->parser->vm, &classInfo.methods); + wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods); + compiler->enclosingClass = NULL; + popScope(compiler); +} + +// Compiles an "import" statement. +// +// An import compiles to a series of instructions. Given: +// +// import "foo" for Bar, Baz +// +// We compile a single IMPORT_MODULE "foo" instruction to load the module +// itself. When that finishes executing the imported module, it leaves the +// ObjModule in vm->lastModule. Then, for Bar and Baz, we: +// +// * Declare a variable in the current scope with that name. +// * Emit an IMPORT_VARIABLE instruction to load the variable's value from the +// other module. +// * Compile the code to store that value in the variable in this scope. +static void import(Compiler* compiler) +{ + ignoreNewlines(compiler); + consume(compiler, TOKEN_STRING, "Expect a string after 'import'."); + int moduleConstant = addConstant(compiler, compiler->parser->previous.value); + + // Load the module. + emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant); + + // Discard the unused result value from calling the module body's closure. + emitOp(compiler, CODE_POP); + + // The for clause is optional. + if (!match(compiler, TOKEN_FOR)) return; + + // Compile the comma-separated list of variables to import. + do + { + ignoreNewlines(compiler); + + consume(compiler, TOKEN_NAME, "Expect variable name."); + + // We need to hold onto the source variable, + // in order to reference it in the import later + Token sourceVariableToken = compiler->parser->previous; + + // Define a string constant for the original variable name. + int sourceVariableConstant = addConstant(compiler, + wrenNewStringLength(compiler->parser->vm, + sourceVariableToken.start, + sourceVariableToken.length)); + + // Store the symbol we care about for the variable + int slot = -1; + if(match(compiler, TOKEN_AS)) + { + //import "module" for Source as Dest + //Use 'Dest' as the name by declaring a new variable for it. + //This parses a name after the 'as' and defines it. + slot = declareNamedVariable(compiler); + } + else + { + //import "module" for Source + //Uses 'Source' as the name directly + slot = declareVariable(compiler, &sourceVariableToken); + } + + // Load the variable from the other module. + emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant); + + // Store the result in the variable here. + defineVariable(compiler, slot); + } while (match(compiler, TOKEN_COMMA)); +} + +// Compiles a "var" variable definition statement. +static void variableDefinition(Compiler* compiler) +{ + // Grab its name, but don't declare it yet. A (local) variable shouldn't be + // in scope in its own initializer. + consume(compiler, TOKEN_NAME, "Expect variable name."); + Token nameToken = compiler->parser->previous; + + // Compile the initializer. + if (match(compiler, TOKEN_EQ)) + { + ignoreNewlines(compiler); + expression(compiler); + } + else + { + // Default initialize it to null. + null(compiler, false); + } + + // Now put it in scope. + int symbol = declareVariable(compiler, &nameToken); + defineVariable(compiler, symbol); +} + +// Compiles a "definition". These are the statements that bind new variables. +// They can only appear at the top level of a block and are prohibited in places +// like the non-curly body of an if or while. +void definition(Compiler* compiler) +{ + if(matchAttribute(compiler)) { + definition(compiler); + return; + } + + if (match(compiler, TOKEN_CLASS)) + { + classDefinition(compiler, false); + return; + } + else if (match(compiler, TOKEN_FOREIGN)) + { + consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); + classDefinition(compiler, true); + return; + } + + disallowAttributes(compiler); + + if (match(compiler, TOKEN_IMPORT)) + { + import(compiler); + } + else if (match(compiler, TOKEN_VAR)) + { + variableDefinition(compiler); + } + else + { + statement(compiler); + } +} + +ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, + bool isExpression, bool printErrors) +{ + // Skip the UTF-8 BOM if there is one. + if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; + + Parser parser; + parser.vm = vm; + parser.module = module; + parser.source = source; + + parser.tokenStart = source; + parser.currentChar = source; + parser.currentLine = 1; + parser.numParens = 0; + + // Zero-init the current token. This will get copied to previous when + // nextToken() is called below. + parser.next.type = TOKEN_ERROR; + parser.next.start = source; + parser.next.length = 0; + parser.next.line = 0; + parser.next.value = UNDEFINED_VAL; + + parser.printErrors = printErrors; + parser.hasError = false; + + // Read the first token into next + nextToken(&parser); + // Copy next -> current + nextToken(&parser); + + int numExistingVariables = module->variables.count; + + Compiler compiler; + initCompiler(&compiler, &parser, NULL, false); + ignoreNewlines(&compiler); + + if (isExpression) + { + expression(&compiler); + consume(&compiler, TOKEN_EOF, "Expect end of expression."); + } + else + { + while (!match(&compiler, TOKEN_EOF)) + { + definition(&compiler); + + // If there is no newline, it must be the end of file on the same line. + if (!matchLine(&compiler)) + { + consume(&compiler, TOKEN_EOF, "Expect end of file."); + break; + } + } + + emitOp(&compiler, CODE_END_MODULE); + } + + emitOp(&compiler, CODE_RETURN); + + // See if there are any implicitly declared module-level variables that never + // got an explicit definition. They will have values that are numbers + // indicating the line where the variable was first used. + for (int i = numExistingVariables; i < parser.module->variables.count; i++) + { + if (IS_NUM(parser.module->variables.data[i])) + { + // Synthesize a token for the original use site. + parser.previous.type = TOKEN_NAME; + parser.previous.start = parser.module->variableNames.data[i]->value; + parser.previous.length = parser.module->variableNames.data[i]->length; + parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]); + error(&compiler, "Variable is used but not defined."); + } + } + + return endCompiler(&compiler, "(script)", 8); +} + +void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) +{ + int ip = 0; + for (;;) + { + Code instruction = (Code)fn->code.data[ip]; + switch (instruction) + { + case CODE_LOAD_FIELD: + case CODE_STORE_FIELD: + case CODE_LOAD_FIELD_THIS: + case CODE_STORE_FIELD_THIS: + // Shift this class's fields down past the inherited ones. We don't + // check for overflow here because we'll see if the number of fields + // overflows when the subclass is created. + fn->code.data[ip + 1] += classObj->superclass->numFields; + 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: + { + // Fill in the constant slot with a reference to the superclass. + int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4]; + fn->constants.data[constant] = OBJ_VAL(classObj->superclass); + break; + } + + case CODE_CLOSURE: + { + // Bind the nested closure too. + int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2]; + wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant])); + break; + } + + case CODE_END: + return; + + default: + // Other instructions are unaffected, so just skip over them. + break; + } + ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip); + } +} + +void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) +{ + wrenGrayValue(vm, compiler->parser->current.value); + wrenGrayValue(vm, compiler->parser->previous.value); + wrenGrayValue(vm, compiler->parser->next.value); + + // Walk up the parent chain to mark the outer compilers too. The VM only + // tracks the innermost one. + do + { + wrenGrayObj(vm, (Obj*)compiler->fn); + wrenGrayObj(vm, (Obj*)compiler->constants); + wrenGrayObj(vm, (Obj*)compiler->attributes); + + if (compiler->enclosingClass != NULL) + { + wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); + + if(compiler->enclosingClass->methodAttributes != NULL) + { + wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes); + } + if(compiler->enclosingClass->classAttributes != NULL) + { + wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes); + } + } + + compiler = compiler->parent; + } + while (compiler != NULL); +} + +// Helpers for Attributes + +// Throw an error if any attributes were found preceding, +// and clear the attributes so the error doesn't keep happening. +static void disallowAttributes(Compiler* compiler) +{ + if (compiler->numAttributes > 0) + { + error(compiler, "Attributes can only specified before a class or a method"); + wrenMapClear(compiler->parser->vm, compiler->attributes); + compiler->numAttributes = 0; + } +} + +// Add an attribute to a given group in the compiler attribues map +static void addToAttributeGroup(Compiler* compiler, + Value group, Value key, Value value) +{ + WrenVM* vm = compiler->parser->vm; + + if(IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group)); + if(IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key)); + if(IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); + + Value groupMapValue = wrenMapGet(compiler->attributes, group); + if(IS_UNDEFINED(groupMapValue)) + { + groupMapValue = OBJ_VAL(wrenNewMap(vm)); + wrenMapSet(vm, compiler->attributes, group, groupMapValue); + } + + //we store them as a map per so we can maintain duplicate keys + //group = { key:[value, ...], } + ObjMap* groupMap = AS_MAP(groupMapValue); + + //var keyItems = group[key] + //if(!keyItems) keyItems = group[key] = [] + Value keyItemsValue = wrenMapGet(groupMap, key); + if(IS_UNDEFINED(keyItemsValue)) + { + keyItemsValue = OBJ_VAL(wrenNewList(vm, 0)); + wrenMapSet(vm, groupMap, key, keyItemsValue); + } + + //keyItems.add(value) + ObjList* keyItems = AS_LIST(keyItemsValue); + wrenValueBufferWrite(vm, &keyItems->elements, value); + + if(IS_OBJ(group)) wrenPopRoot(vm); + if(IS_OBJ(key)) wrenPopRoot(vm); + if(IS_OBJ(value)) wrenPopRoot(vm); +} + + +// Emit the attributes in the give map onto the stack +static void emitAttributes(Compiler* compiler, ObjMap* attributes) +{ + // Instantiate a new map for the attributes + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + // The attributes are stored as group = { key:[value, value, ...] } + // so our first level is the group map + for(uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++) + { + const MapEntry* groupEntry = &attributes->entries[groupIdx]; + if(IS_UNDEFINED(groupEntry->key)) continue; + //group key + emitConstant(compiler, groupEntry->key); + + //group value is gonna be a map + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + ObjMap* groupItems = AS_MAP(groupEntry->value); + for(uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++) + { + const MapEntry* itemEntry = &groupItems->entries[itemIdx]; + if(IS_UNDEFINED(itemEntry->key)) continue; + + emitConstant(compiler, itemEntry->key); + // Attribute key value, key = [] + loadCoreVariable(compiler, "List"); + callMethod(compiler, 0, "new()", 5); + // Add the items to the key list + ObjList* items = AS_LIST(itemEntry->value); + for(int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx) + { + emitConstant(compiler, items->elements.data[itemIdx]); + callMethod(compiler, 1, "addCore_(_)", 11); + } + // Add the list to the map + callMethod(compiler, 2, "addCore_(_,_)", 13); + } + + // Add the key/value to the map + callMethod(compiler, 2, "addCore_(_,_)", 13); + } + +} + +// Methods are stored as method <-> attributes, so we have to have +// an indirection to resolve for methods +static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes) +{ + // Instantiate a new map for the attributes + loadCoreVariable(compiler, "Map"); + callMethod(compiler, 0, "new()", 5); + + for(uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++) + { + const MapEntry* methodEntry = &attributes->entries[methodIdx]; + if(IS_UNDEFINED(methodEntry->key)) continue; + emitConstant(compiler, methodEntry->key); + ObjMap* attributeMap = AS_MAP(methodEntry->value); + emitAttributes(compiler, attributeMap); + callMethod(compiler, 2, "addCore_(_,_)", 13); + } +} + + +// Emit the final ClassAttributes that exists at runtime +static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo) +{ + loadCoreVariable(compiler, "ClassAttributes"); + + classInfo->classAttributes + ? emitAttributes(compiler, classInfo->classAttributes) + : null(compiler, false); + + classInfo->methodAttributes + ? emitAttributeMethods(compiler, classInfo->methodAttributes) + : null(compiler, false); + + callMethod(compiler, 2, "new(_,_)", 8); +} + +// Copy the current attributes stored in the compiler into a destination map +// This also resets the counter, since the intent is to consume the attributes +static void copyAttributes(Compiler* compiler, ObjMap* into) +{ + compiler->numAttributes = 0; + + if(compiler->attributes->count == 0) return; + if(into == NULL) return; + + WrenVM* vm = compiler->parser->vm; + + // Note we copy the actual values as is since we'll take ownership + // and clear the original map + for(uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++) + { + const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx]; + if(IS_UNDEFINED(attrEntry->key)) continue; + wrenMapSet(vm, into, attrEntry->key, attrEntry->value); + } + + wrenMapClear(vm, compiler->attributes); +} + +// Copy the current attributes stored in the compiler into the method specific +// attributes for the current enclosingClass. +// This also resets the counter, since the intent is to consume the attributes +static void copyMethodAttributes(Compiler* compiler, bool isForeign, + bool isStatic, const char* fullSignature, int32_t length) +{ + compiler->numAttributes = 0; + + if(compiler->attributes->count == 0) return; + + WrenVM* vm = compiler->parser->vm; + + // Make a map for this method to copy into + ObjMap* methodAttr = wrenNewMap(vm); + wrenPushRoot(vm, (Obj*)methodAttr); + copyAttributes(compiler, methodAttr); + + // Include 'foreign static ' in front as needed + int32_t fullLength = length; + if(isForeign) fullLength += 8; + if(isStatic) fullLength += 7; + char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7]; + const char* foreignPrefix = isForeign ? "foreign " : ""; + const char* staticPrefix = isStatic ? "static " : ""; + sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix, + length, fullSignature); + fullSignatureWithPrefix[fullLength] = '\0'; + + if(compiler->enclosingClass->methodAttributes == NULL) { + compiler->enclosingClass->methodAttributes = wrenNewMap(vm); + } + + // Store the method attributes in the class map + Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength); + wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr)); + + wrenPopRoot(vm); +} +// End file "wren_compiler.c" +// Begin file "wren_primitive.c" + +#include + +// 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; +} +// End file "wren_primitive.c" +// Begin file "wren_opt_meta.c" + +#if WREN_OPT_META + +#include + +// Begin file "wren_opt_meta.wren.inc" +// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit. +static const char* metaModuleSource = +"class Meta {\n" +" static getModuleVariables(module) {\n" +" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" +" var result = getModuleVariables_(module)\n" +" if (result != null) return result\n" +"\n" +" Fiber.abort(\"Could not find a module named '%(module)'.\")\n" +" }\n" +"\n" +" static eval(source) {\n" +" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" +"\n" +" var closure = compile_(source, false, false)\n" +" // TODO: Include compile errors.\n" +" if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" +"\n" +" closure.call()\n" +" }\n" +"\n" +" static compileExpression(source) {\n" +" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" +" return compile_(source, true, true)\n" +" }\n" +"\n" +" static compile(source) {\n" +" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" +" return compile_(source, false, true)\n" +" }\n" +"\n" +" foreign static compile_(source, isExpression, printErrors)\n" +" foreign static getModuleVariables_(module)\n" +"}\n"; +// End file "wren_opt_meta.wren.inc" + +void metaCompile(WrenVM* vm) +{ + const char* source = wrenGetSlotString(vm, 1); + bool isExpression = wrenGetSlotBool(vm, 2); + bool printErrors = wrenGetSlotBool(vm, 3); + + // TODO: Allow passing in module? + // Look up the module surrounding the callsite. This is brittle. The -2 walks + // up the callstack assuming that the meta module has one level of + // indirection before hitting the user's code. Any change to meta may require + // this constant to be tweaked. + ObjFiber* currentFiber = vm->fiber; + ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; + ObjString* module = fn->module->name; + + ObjClosure* closure = wrenCompileSource(vm, module->value, source, + isExpression, printErrors); + + // Return the result. We can't use the public API for this since we have a + // bare ObjClosure*. + if (closure == NULL) + { + vm->apiStack[0] = NULL_VAL; + } + else + { + vm->apiStack[0] = OBJ_VAL(closure); + } +} + +void metaGetModuleVariables(WrenVM* vm) +{ + wrenEnsureSlots(vm, 3); + + Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); + if (IS_UNDEFINED(moduleValue)) + { + vm->apiStack[0] = NULL_VAL; + return; + } + + ObjModule* module = AS_MODULE(moduleValue); + ObjList* names = wrenNewList(vm, module->variableNames.count); + vm->apiStack[0] = OBJ_VAL(names); + + // Initialize the elements to null in case a collection happens when we + // allocate the strings below. + for (int i = 0; i < names->elements.count; i++) + { + names->elements.data[i] = NULL_VAL; + } + + for (int i = 0; i < names->elements.count; i++) + { + names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); + } +} + +const char* wrenMetaSource() +{ + return metaModuleSource; +} + +WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, + const char* className, + bool isStatic, + const char* signature) +{ + // There is only one foreign method in the meta module. + ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); + ASSERT(isStatic, "Should be static."); + + if (strcmp(signature, "compile_(_,_,_)") == 0) + { + return metaCompile; + } + + if (strcmp(signature, "getModuleVariables_(_)") == 0) + { + return metaGetModuleVariables; + } + + ASSERT(false, "Unknown method."); + return NULL; +} + +#endif +// End file "wren_opt_meta.c" +// Begin file "wren_opt_random.c" + +#if WREN_OPT_RANDOM + +#include +#include + + +// Begin file "wren_opt_random.wren.inc" +// Generated automatically from src/optional/wren_opt_random.wren. Do not edit. +static const char* randomModuleSource = +"foreign class Random {\n" +" construct new() {\n" +" seed_()\n" +" }\n" +"\n" +" construct new(seed) {\n" +" if (seed is Num) {\n" +" seed_(seed)\n" +" } else if (seed is Sequence) {\n" +" if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" +"\n" +" // TODO: Empty sequence.\n" +" var seeds = []\n" +" for (element in seed) {\n" +" if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" +"\n" +" seeds.add(element)\n" +" if (seeds.count == 16) break\n" +" }\n" +"\n" +" // Cycle the values to fill in any missing slots.\n" +" var i = 0\n" +" while (seeds.count < 16) {\n" +" seeds.add(seeds[i])\n" +" i = i + 1\n" +" }\n" +"\n" +" seed_(\n" +" seeds[0], seeds[1], seeds[2], seeds[3],\n" +" seeds[4], seeds[5], seeds[6], seeds[7],\n" +" seeds[8], seeds[9], seeds[10], seeds[11],\n" +" seeds[12], seeds[13], seeds[14], seeds[15])\n" +" } else {\n" +" Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" +" }\n" +" }\n" +"\n" +" foreign seed_()\n" +" foreign seed_(seed)\n" +" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" +"\n" +" foreign float()\n" +" float(end) { float() * end }\n" +" float(start, end) { float() * (end - start) + start }\n" +"\n" +" foreign int()\n" +" int(end) { (float() * end).floor }\n" +" int(start, end) { (float() * (end - start)).floor + start }\n" +"\n" +" sample(list) {\n" +" if (list.count == 0) Fiber.abort(\"Not enough elements to sample.\")\n" +" return list[int(list.count)]\n" +" }\n" +" sample(list, count) {\n" +" if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" +"\n" +" var result = []\n" +"\n" +" // The algorithm described in \"Programming pearls: a sample of brilliance\".\n" +" // Use a hash map for sample sizes less than 1/4 of the population size and\n" +" // an array of booleans for larger samples. This simple heuristic improves\n" +" // performance for large sample sizes as well as reduces memory usage.\n" +" if (count * 4 < list.count) {\n" +" var picked = {}\n" +" for (i in list.count - count...list.count) {\n" +" var index = int(i + 1)\n" +" if (picked.containsKey(index)) index = i\n" +" picked[index] = true\n" +" result.add(list[index])\n" +" }\n" +" } else {\n" +" var picked = List.filled(list.count, false)\n" +" for (i in list.count - count...list.count) {\n" +" var index = int(i + 1)\n" +" if (picked[index]) index = i\n" +" picked[index] = true\n" +" result.add(list[index])\n" +" }\n" +" }\n" +"\n" +" return result\n" +" }\n" +"\n" +" shuffle(list) {\n" +" if (list.isEmpty) return\n" +"\n" +" // Fisher-Yates shuffle.\n" +" for (i in 0...list.count - 1) {\n" +" var from = int(i, list.count)\n" +" var temp = list[from]\n" +" list[from] = list[i]\n" +" list[i] = temp\n" +" }\n" +" }\n" +"}\n"; +// End file "wren_opt_random.wren.inc" + +// Implements the well equidistributed long-period linear PRNG (WELL512a). +// +// https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear +typedef struct +{ + uint32_t state[16]; + uint32_t index; +} Well512; + +// Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf +static uint32_t advanceState(Well512* well) +{ + uint32_t a, b, c, d; + a = well->state[well->index]; + c = well->state[(well->index + 13) & 15]; + b = a ^ c ^ (a << 16) ^ (c << 15); + c = well->state[(well->index + 9) & 15]; + c ^= (c >> 11); + a = well->state[well->index] = b ^ c; + d = a ^ ((a << 5) & 0xda442d24U); + + well->index = (well->index + 15) & 15; + a = well->state[well->index]; + well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); + return well->state[well->index]; +} + +static void randomAllocate(WrenVM* vm) +{ + Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); + well->index = 0; +} + +static void randomSeed0(WrenVM* vm) +{ + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + srand((uint32_t)time(NULL)); + for (int i = 0; i < 16; i++) + { + well->state[i] = rand(); + } +} + +static void randomSeed1(WrenVM* vm) +{ + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + srand((uint32_t)wrenGetSlotDouble(vm, 1)); + for (int i = 0; i < 16; i++) + { + well->state[i] = rand(); + } +} + +static void randomSeed16(WrenVM* vm) +{ + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + for (int i = 0; i < 16; i++) + { + well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); + } +} + +static void randomFloat(WrenVM* vm) +{ + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + // A double has 53 bits of precision in its mantissa, and we'd like to take + // full advantage of that, so we need 53 bits of random source data. + + // First, start with 32 random bits, shifted to the left 21 bits. + double result = (double)advanceState(well) * (1 << 21); + + // Then add another 21 random bits. + result += (double)(advanceState(well) & ((1 << 21) - 1)); + + // Now we have a number from 0 - (2^53). Divide be the range to get a double + // from 0 to 1.0 (half-inclusive). + result /= 9007199254740992.0; + + wrenSetSlotDouble(vm, 0, result); +} + +static void randomInt0(WrenVM* vm) +{ + Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); + + wrenSetSlotDouble(vm, 0, (double)advanceState(well)); +} + +const char* wrenRandomSource() +{ + return randomModuleSource; +} + +WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, + const char* module, + const char* className) +{ + ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); + WrenForeignClassMethods methods; + methods.allocate = randomAllocate; + methods.finalize = NULL; + return methods; +} + +WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, + const char* className, + bool isStatic, + const char* signature) +{ + ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); + + if (strcmp(signature, "") == 0) return randomAllocate; + if (strcmp(signature, "seed_()") == 0) return randomSeed0; + if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; + + if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) + { + return randomSeed16; + } + + if (strcmp(signature, "float()") == 0) return randomFloat; + if (strcmp(signature, "int()") == 0) return randomInt0; + + ASSERT(false, "Unknown method."); + return NULL; +} + +#endif +// End file "wren_opt_random.c" diff --git a/include/wren.h b/include/wren.h new file mode 100644 index 00000000..7845911c --- /dev/null +++ b/include/wren.h @@ -0,0 +1,554 @@ +#ifndef wren_h +#define wren_h + +#include +#include +#include + +// The Wren semantic version number components. +#define WREN_VERSION_MAJOR 0 +#define WREN_VERSION_MINOR 4 +#define WREN_VERSION_PATCH 0 + +// A human-friendly string representation of the version. +#define WREN_VERSION_STRING "0.4.0" + +// A monotonically increasing numeric representation of the version number. Use +// this if you want to do range checks over versions. +#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ + WREN_VERSION_MINOR * 1000 + \ + WREN_VERSION_PATCH) + +#ifndef WREN_API + #if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT) + #define WREN_API __declspec( dllexport ) + #else + #define WREN_API + #endif +#endif //WREN_API + +// A single virtual machine for executing Wren code. +// +// Wren has no global state, so all state stored by a running interpreter lives +// here. +typedef struct WrenVM WrenVM; + +// A handle to a Wren object. +// +// This lets code outside of the VM hold a persistent reference to an object. +// After a handle is acquired, and until it is released, this ensures the +// garbage collector will not reclaim the object it references. +typedef struct WrenHandle WrenHandle; + +// A generic allocation function that handles all explicit memory management +// used by Wren. It's used like so: +// +// - To allocate new memory, [memory] is NULL and [newSize] is the desired +// size. It should return the allocated memory or NULL on failure. +// +// - To attempt to grow an existing allocation, [memory] is the memory, 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] 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] will be +// zero. It should return NULL. +typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData); + +// A function callable from Wren code, but implemented in C. +typedef void (*WrenForeignMethodFn)(WrenVM* vm); + +// A finalizer function for freeing resources owned by an instance of a foreign +// class. Unlike most foreign methods, finalizers do not have access to the VM +// and should not interact with it since it's in the middle of a garbage +// collection. +typedef void (*WrenFinalizerFn)(void* data); + +// Gives the host a chance to canonicalize the imported module name, +// potentially taking into account the (previously resolved) name of the module +// that contains the import. Typically, this is used to implement relative +// imports. +typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, + const char* importer, const char* name); + +// Forward declare +struct WrenLoadModuleResult; + +// Called after loadModuleFn is called for module [name]. The original returned result +// is handed back to you in this callback, so that you can free memory if appropriate. +typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result); + +// The result of a loadModuleFn call. +// [source] is the source code for the module, or NULL if the module is not found. +// [onComplete] an optional callback that will be called once Wren is done with the result. +typedef struct WrenLoadModuleResult +{ + const char* source; + WrenLoadModuleCompleteFn onComplete; + void* userData; +} WrenLoadModuleResult; + +// Loads and returns the source code for the module [name]. +typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name); + +// Returns a pointer to a foreign method on [className] in [module] with +// [signature]. +typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, + const char* module, const char* className, bool isStatic, + const char* signature); + +// Displays a string of text to the user. +typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); + +typedef enum +{ + // A syntax or resolution error detected at compile time. + WREN_ERROR_COMPILE, + + // The error message for a runtime error. + WREN_ERROR_RUNTIME, + + // One entry of a runtime error's stack trace. + WREN_ERROR_STACK_TRACE +} WrenErrorType; + +// Reports an error to the user. +// +// An error detected during compile time is reported by calling this once with +// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] +// where the error occurs, and the compiler's error [message]. +// +// A runtime error is reported by calling this once with [type] +// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's +// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are +// made for each line in the stack trace. Each of those has the resolved +// [module] and [line] where the method or function is defined and [message] is +// the name of the method or function. +typedef void (*WrenErrorFn)( + WrenVM* vm, WrenErrorType type, const char* module, int line, + const char* message); + +typedef struct +{ + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenSetSlotNewForeign()] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collect a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenFinalizerFn finalize; +} WrenForeignClassMethods; + +// Returns a pair of pointers to the foreign methods used to allocate and +// finalize the data for instances of [className] in resolved [module]. +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( + WrenVM* vm, const char* module, const char* className); + +typedef struct +{ + // The callback Wren will use to allocate, reallocate, and deallocate memory. + // + // If `NULL`, defaults to a built-in function that uses `realloc` and `free`. + WrenReallocateFn reallocateFn; + + // The callback Wren uses to resolve a module name. + // + // Some host applications may wish to support "relative" imports, where the + // meaning of an import string depends on the module that contains it. To + // support that without baking any policy into Wren itself, the VM gives the + // host a chance to resolve an import string. + // + // Before an import is loaded, it calls this, passing in the name of the + // module that contains the import and the import string. The host app can + // look at both of those and produce a new "canonical" string that uniquely + // identifies the module. This string is then used as the name of the module + // going forward. It is what is passed to [loadModuleFn], how duplicate + // imports of the same module are detected, and how the module is reported in + // stack traces. + // + // If you leave this function NULL, then the original import string is + // treated as the resolved string. + // + // If an import cannot be resolved by the embedder, it should return NULL and + // Wren will report that as a runtime error. + // + // Wren will take ownership of the string you return and free it for you, so + // it should be allocated using the same allocation function you provide + // above. + WrenResolveModuleFn resolveModuleFn; + + // The callback Wren uses to load a module. + // + // Since Wren does not talk directly to the file system, it relies on the + // embedder to physically locate and read the source code for a module. The + // first time an import appears, Wren will call this and pass in the name of + // the module being imported. The method will return a result, which contains + // the source code for that module. Memory for the source is owned by the + // host application, and can be freed using the onComplete callback. + // + // This will only be called once for any given module name. Wren caches the + // result internally so subsequent imports of the same module will use the + // previous source and not call this. + // + // If a module with the given name could not be found by the embedder, it + // should return NULL and Wren will report that as a runtime error. + WrenLoadModuleFn loadModuleFn; + + // The callback Wren uses to find a foreign method and bind it to a class. + // + // When a foreign method is declared in a class, this will be called with the + // foreign method's module, class, and signature when the class body is + // executed. It should return a pointer to the foreign function that will be + // bound to that method. + // + // If the foreign function could not be found, this should return NULL and + // Wren will report it as runtime error. + WrenBindForeignMethodFn bindForeignMethodFn; + + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + + // The callback Wren uses to display text when `System.print()` or the other + // related functions are called. + // + // If this is `NULL`, Wren discards any printed text. + WrenWriteFn writeFn; + + // The callback Wren uses to report errors. + // + // When an error occurs, this will be called with the module name, line + // number, and an error message. If this is `NULL`, Wren doesn't report any + // errors. + WrenErrorFn errorFn; + + // The number of bytes Wren will allocate before triggering the first garbage + // collection. + // + // If zero, defaults to 10MB. + size_t initialHeapSize; + + // After a collection occurs, the threshold for the next collection is + // determined based on the number of bytes remaining in use. This allows Wren + // to shrink its memory usage automatically after reclaiming a large amount + // of memory. + // + // This can be used to ensure that the heap does not get too small, which can + // in turn lead to a large number of collections afterwards as the heap grows + // back to a usable size. + // + // If zero, defaults to 1MB. + size_t minHeapSize; + + // Wren will resize the heap automatically as the number of bytes + // remaining in use after a collection changes. This number determines the + // amount of additional memory Wren will use after a collection, as a + // percentage of the current heap size. + // + // For example, say that this is 50. After a garbage collection, when there + // are 400 bytes of memory still in use, the next collection will be triggered + // after a total of 600 bytes are allocated (including the 400 already in + // use.) + // + // Setting this to a smaller number wastes less memory, but triggers more + // frequent garbage collections. + // + // If zero, defaults to 50. + int heapGrowthPercent; + + // User-defined data associated with the VM. + void* userData; + +} WrenConfiguration; + +typedef enum +{ + WREN_RESULT_SUCCESS, + WREN_RESULT_COMPILE_ERROR, + WREN_RESULT_RUNTIME_ERROR +} WrenInterpretResult; + +// The type of an object stored in a slot. +// +// This is not necessarily the object's *class*, but instead its low level +// representation type. +typedef enum +{ + WREN_TYPE_BOOL, + WREN_TYPE_NUM, + WREN_TYPE_FOREIGN, + WREN_TYPE_LIST, + WREN_TYPE_MAP, + WREN_TYPE_NULL, + WREN_TYPE_STRING, + + // The object is of a type that isn't accessible by the C API. + WREN_TYPE_UNKNOWN +} WrenType; + +// Get the current wren version number. +// +// Can be used to range checks over versions. +WREN_API int wrenGetVersionNumber(); + +// Initializes [configuration] with all of its default values. +// +// Call this before setting the particular fields you care about. +WREN_API void wrenInitConfiguration(WrenConfiguration* configuration); + +// Creates a new Wren virtual machine using the given [configuration]. Wren +// will copy the configuration data, so the argument passed to this can be +// freed after calling this. If [configuration] is `NULL`, uses a default +// configuration. +WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration); + +// Disposes of all resources is use by [vm], which was previously created by a +// call to [wrenNewVM]. +WREN_API void wrenFreeVM(WrenVM* vm); + +// Immediately run the garbage collector to free unused memory. +WREN_API void wrenCollectGarbage(WrenVM* vm); + +// Runs [source], a string of Wren source code in a new fiber in [vm] in the +// context of resolved [module]. +WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, + const char* source); + +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. +// +// This handle can be used repeatedly to directly invoke that method from C +// code using [wrenCall]. +// +// When you are done with this handle, it must be released using +// [wrenReleaseHandle]. +WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); + +// Calls [method], using the receiver and arguments previously set up on the +// stack. +// +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. +// +// After this returns, you can access the return value from slot 0 on the stack. +WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); + +// Releases the reference stored in [handle]. After calling this, [handle] can +// no longer be used. +WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); + +// The following functions are intended to be called from foreign methods or +// finalizers. The interface Wren provides to a foreign method is like a +// register machine: you are given a numbered array of slots that values can be +// read from and written to. Values always live in a slot (unless explicitly +// captured using wrenGetSlotHandle(), which ensures the garbage collector can +// find them. +// +// When your foreign function is called, you are given one slot for the receiver +// and each argument to the method. The receiver is in slot 0 and the arguments +// are in increasingly numbered slots after that. You are free to read and +// write to those slots as you want. If you want more slots to use as scratch +// space, you can call wrenEnsureSlots() to add more. +// +// When your function returns, every slot except slot zero is discarded and the +// value in slot zero is used as the return value of the method. If you don't +// store a return value in that slot yourself, it will retain its previous +// value, the receiver. +// +// While Wren is dynamically typed, C is not. This means the C interface has to +// support the various types of primitive values a Wren variable can hold: bool, +// double, string, etc. If we supported this for every operation in the C API, +// there would be a combinatorial explosion of functions, like "get a +// double-valued element from a list", "insert a string key and double value +// into a map", etc. +// +// To avoid that, the only way to convert to and from a raw C value is by going +// into and out of a slot. All other functions work with values already in a +// slot. So, to add an element to a list, you put the list in one slot, and the +// element in another. Then there is a single API function wrenInsertInList() +// that takes the element out of that slot and puts it into the list. +// +// The goal of this API is to be easy to use while not compromising performance. +// The latter means it does not do type or bounds checking at runtime except +// using assertions which are generally removed from release builds. C is an +// unsafe language, so it's up to you to be careful to use it correctly. In +// return, you get a very fast FFI. + +// Returns the number of slots available to the current foreign method. +WREN_API int wrenGetSlotCount(WrenVM* vm); + +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. +// +// Does not shrink the stack if it has more than enough slots. +// +// It is an error to call this from a finalizer. +WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots); + +// Gets the type of the object in [slot]. +WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot); + +// Reads a boolean value from [slot]. +// +// It is an error to call this if the slot does not contain a boolean value. +WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot); + +// Reads a byte array from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// Returns a pointer to the first byte of the array and fill [length] with the +// number of bytes in the array. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); + +// Reads a number from [slot]. +// +// It is an error to call this if the slot does not contain a number. +WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot); + +// Reads a foreign object from [slot] and returns a pointer to the foreign data +// stored with it. +// +// It is an error to call this if the slot does not contain an instance of a +// foreign class. +WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot); + +// Reads a string from [slot]. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign method, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +// +// It is an error to call this if the slot does not contain a string. +WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot); + +// Creates a handle for the value stored in [slot]. +// +// This will prevent the object that is referred to from being garbage collected +// until the handle is released by calling [wrenReleaseHandle()]. +WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); + +// Stores the boolean [value] in [slot]. +WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value); + +// Stores the array [length] of [bytes] in [slot]. +// +// The bytes are copied to a new string within Wren's heap, so you can free +// memory used by them after this is called. +WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); + +// Stores the numeric [value] in [slot]. +WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value); + +// Creates a new instance of the foreign class stored in [classSlot] with [size] +// bytes of raw storage and places the resulting object in [slot]. +// +// This does not invoke the foreign class's constructor on the new instance. If +// you need that to happen, call the constructor from Wren, which will then +// call the allocator foreign method. In there, call this to create the object +// and then the constructor will be invoked when the allocator returns. +// +// Returns a pointer to the foreign object's data. +WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); + +// Stores a new empty list in [slot]. +WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot); + +// Stores a new empty map in [slot]. +WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot); + +// Stores null in [slot]. +WREN_API void wrenSetSlotNull(WrenVM* vm, int slot); + +// Stores the string [text] in [slot]. +// +// The [text] is copied to a new string within Wren's heap, so you can free +// memory used by it after this is called. The length is calculated using +// [strlen()]. If the string may contain any null bytes in the middle, then you +// should use [wrenSetSlotBytes()] instead. +WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text); + +// Stores the value captured in [handle] in [slot]. +// +// This does not release the handle for the value. +WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); + +// Returns the number of elements in the list stored in [slot]. +WREN_API int wrenGetListCount(WrenVM* vm, int slot); + +// Reads element [index] from the list in [listSlot] and stores it in +// [elementSlot]. +WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Sets the value stored at [index] in the list at [listSlot], +// to the value from [elementSlot]. +WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + +// Returns the number of entries in the map stored in [slot]. +WREN_API int wrenGetMapCount(WrenVM* vm, int slot); + +// Returns true if the key in [keySlot] is found in the map placed in [mapSlot]. +WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot); + +// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and +// stores it in [valueSlot]. +WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Takes the value stored at [valueSlot] and inserts it into the map stored +// at [mapSlot] with key [keySlot]. +WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot); + +// Removes a value from the map in [mapSlot], with the key from [keySlot], +// and place it in [removedValueSlot]. If not found, [removedValueSlot] is +// set to null, the same behaviour as the Wren Map API. +WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, + int removedValueSlot); + +// Looks up the top level variable with [name] in resolved [module] and stores +// it in [slot]. +WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, + int slot); + +// Looks up the top level variable with [name] in resolved [module], +// returns false if not found. The module must be imported at the time, +// use wrenHasModule to ensure that before calling. +WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name); + +// Returns true if [module] has been imported/resolved before, false if not. +WREN_API bool wrenHasModule(WrenVM* vm, const char* module); + +// Sets the current fiber to be aborted, and uses the value in [slot] as the +// runtime error object. +WREN_API void wrenAbortFiber(WrenVM* vm, int slot); + +// Returns the user data associated with the WrenVM. +WREN_API void* wrenGetUserData(WrenVM* vm); + +// Sets user data associated with the WrenVM. +WREN_API void wrenSetUserData(WrenVM* vm, void* userData); + +#endif diff --git a/lib/wren b/lib/wren deleted file mode 160000 index f09ebf6a..00000000 --- a/lib/wren +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f09ebf6accdf73210bb0c9540e171c21e472f236 diff --git a/scripts/generateEmbedModules.sh b/scripts/generateEmbedModules.sh index 9c569cec..c82bc470 100755 --- a/scripts/generateEmbedModules.sh +++ b/scripts/generateEmbedModules.sh @@ -1,7 +1,5 @@ #!/bin/bash -cd src/util - -gcc embed.c -o embed -std=gnu99 +cd src/tools declare -a arr=( "stringUtils" @@ -20,7 +18,7 @@ declare -a arr=( "platform" "random" ) - + declare -a opts=( ) diff --git a/scripts/setup_wren.sh b/scripts/setup_wren.sh deleted file mode 100755 index 8d90f8a1..00000000 --- a/scripts/setup_wren.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -DOME_DIR=$PWD -INCLUDE_DIR=$DOME_DIR/include -LIB_DIR=$DOME_DIR/lib -WREN_DIR=$LIB_DIR/wren - -cd "$WREN_DIR/projects" - -if [[ "$OSTYPE" == "linux-gnu"* ]]; then - # ... - cd make -elif [[ "$OSTYPE" == "darwin"* ]]; then - cd make.mac - # Mac OSX -elif [[ "$OSTYPE" == "msys" ]]; then - cd make - # Lightweight shell and GNU utilities compiled for Windows (part of MinGW) -elif [[ "$OSTYPE" == "win32" ]]; then - exit 1 -else - exit 1 -fi - -# Undo external makefile flags just in case -unset config -MAKEFLAGS="--no-print-directory" -# build the debug version of wren -make clean -CFLAGS=-fvisibility=hidden -make ${@:2} CFLAGS=${CFLAGS} verbose=1 config=debug_$1 wren && cp "$WREN_DIR/lib/libwren_d.a" "$LIB_DIR/libwrend.a" -# build the release version of wren -make clean -make ${@:2} CFLAGS=${CFLAGS} verbose=1 config=release_$1 wren && cp "$WREN_DIR/lib/libwren.a" "$LIB_DIR/libwren.a" -# Copy the wren.h to our includes -cp "$WREN_DIR/src/include/wren.h" "$INCLUDE_DIR/wren.h" diff --git a/scripts/setup_wren_web.sh b/scripts/setup_wren_web.sh index c41e522a..68b9ef43 100755 --- a/scripts/setup_wren_web.sh +++ b/scripts/setup_wren_web.sh @@ -5,8 +5,5 @@ LIB_DIR=$DOME_DIR/lib OBJ_DIR=$DOME_DIR/obj WREN_DIR=$LIB_DIR/wren -cd "$WREN_DIR/util" -./generate_amalgamation.py > "$LIB_DIR/wren_all.c" cd "$DOME_DIR" -emcc -c "$LIB_DIR/wren_all.c" -o "$OBJ_DIR/web/wren.o" -cp "$WREN_DIR/src/include/wren.h" "$INCLUDE_DIR/wren.h" +emcc -c "$INCLUDE_DIR/wren.c" -o "$OBJ_DIR/web/wren.o" diff --git a/src/audio/api.c b/src/api/audio.c similarity index 100% rename from src/audio/api.c rename to src/api/audio.c diff --git a/src/api/bitmap.c b/src/api/bitmap.c new file mode 100644 index 00000000..8fa30474 --- /dev/null +++ b/src/api/bitmap.c @@ -0,0 +1,60 @@ +internal DOME_Bitmap* +BITMAP_API_fromFileInMemory(DOME_Context ctx, void* buffer, size_t length) { + DOME_Bitmap* bitmap = malloc(sizeof(DOME_Bitmap)); + if (bitmap == NULL) { + char* reason = strerror(errno); + size_t length = strlen(reason) + 1; + PLUGIN_COLLECTION_setErrorReason((ENGINE*)ctx, reason, length); + return NULL; + } + + bitmap->pixels = (DOME_Color*)stbi_load_from_memory((const stbi_uc*)buffer, length, + &bitmap->width, &bitmap->height, + &bitmap->channels, STBI_rgb_alpha); + if (bitmap->pixels == NULL) { + free(bitmap); + bitmap = NULL; + char* reason = stbi_failure_reason(); + size_t length = strlen(reason) + 1; + PLUGIN_COLLECTION_setErrorReason((ENGINE*)ctx, reason, length); + } + return bitmap; +} + +internal DOME_Bitmap* +BITMAP_API_fromFile(DOME_Context ctx, const char* path) { + size_t length; + void* buffer = IO_API_readFile(ctx, path, &length); + return BITMAP_API_fromFileInMemory(ctx, buffer, length); +} + +internal DOME_Color +BITMAP_API_pget(DOME_Bitmap* bitmap, uint32_t x, uint32_t y) { + assert(y < bitmap->height); + assert(x < bitmap->width); + return bitmap->pixels[y * bitmap->width + x]; +} + +internal void +BITMAP_API_pset(DOME_Bitmap* bitmap, uint32_t x, uint32_t y, DOME_Color color) { + assert(y < bitmap->height); + assert(x < bitmap->width); + bitmap->pixels[y * bitmap->width + x] = color; +} + +internal void +BITMAP_API_free(DOME_Bitmap* bitmap) { + if (bitmap->pixels != NULL) { + free(bitmap->pixels); + } + free(bitmap); +} + +BITMAP_API_v0 bitmap_v0 = { + .fromFileInMemory = BITMAP_API_fromFileInMemory, + .fromFile = BITMAP_API_fromFile, + .pget = BITMAP_API_pget, + .pset = BITMAP_API_pset, + .free = BITMAP_API_free +}; + diff --git a/src/api/canvas.c b/src/api/canvas.c new file mode 100644 index 00000000..15098649 --- /dev/null +++ b/src/api/canvas.c @@ -0,0 +1,83 @@ +internal void +CANVAS_API_unsafePset(DOME_Context ctx, int32_t x, int32_t y, DOME_Color color) { + ENGINE* engine = (ENGINE*)ctx; + ENGINE_unsafePset(engine, x, y, color.value); +} + +internal void +CANVAS_API_pset(DOME_Context ctx, int32_t x, int32_t y, DOME_Color color) { + ENGINE* engine = (ENGINE*)ctx; + ENGINE_pset(engine, x, y, color.value); +} + +internal DOME_Color +CANVAS_API_pget(DOME_Context ctx, int32_t x, int32_t y) { + ENGINE* engine = (ENGINE*)ctx; + DOME_Color color; + color.value = ENGINE_pget(engine, x, y); + return color; +} + +internal uint32_t +CANVAS_API_getWidth(DOME_Context ctx) { + ENGINE* engine = (ENGINE*)ctx; + return engine->canvas.width; +} +internal uint32_t +CANVAS_API_getHeight(DOME_Context ctx) { + ENGINE* engine = (ENGINE*)ctx; + return engine->canvas.height; +} + +internal void +CANVAS_API_draw(DOME_Context ctx, DOME_Bitmap* bitmap, int32_t x, int32_t y, DOME_DrawMode mode) { + ENGINE* engine = (ENGINE*)ctx; + if ((mode & DOME_DRAWMODE_BLEND) != 0) { + // do alpha blending. slow. + for (int32_t j = 0; j < bitmap->height; j++) { + for (int32_t i = 0; i < bitmap->height; i++) { + DOME_Color c = *(bitmap->pixels + (j * bitmap->width) + i); + ENGINE_pset(engine, x + i, y + j, c.value); + } + } + } else { + // fast blit + int32_t height = bitmap->height; + int32_t width = bitmap->width; + DOME_Color* pixels = bitmap->pixels; + for (int32_t j = 0; j < height; j++) { + uint32_t* row = (uint32_t*)(pixels + (j * width)); + ENGINE_blitLine(engine, x, y + j, width, row); + } + } +} + +internal void +CANVAS_API_line(DOME_Context ctx, int64_t x0, int64_t y0, int64_t x1, int64_t y1, DOME_Color color) { + ENGINE* engine = (ENGINE*)ctx; + ENGINE_line(engine, x0, y0, x1, y1, color.value, 1); +} + +internal void +CANVAS_API_rect(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color) { + ENGINE* engine = (ENGINE*)ctx; + ENGINE_rect(engine, x, y, width, height, color.value); +} + +internal void +CANVAS_API_rectfill(DOME_Context ctx, int64_t x, int64_t y, int64_t width, int64_t height, DOME_Color color) { + ENGINE* engine = (ENGINE*)ctx; + ENGINE_rectfill(engine, x, y, width, height, color.value); +} + +CANVAS_API_v0 canvas_v0 = { + .pget = CANVAS_API_pget, + .pset = CANVAS_API_pset, + .unsafePset = CANVAS_API_unsafePset, + .getWidth = CANVAS_API_getWidth, + .getHeight = CANVAS_API_getHeight, + .draw = CANVAS_API_draw, + .line = CANVAS_API_line, + .rect = CANVAS_API_rect, + .rectfill = CANVAS_API_rectfill +}; diff --git a/src/api/core.c b/src/api/core.c new file mode 100644 index 00000000..4646cb30 --- /dev/null +++ b/src/api/core.c @@ -0,0 +1,78 @@ +internal DOME_Result +DOME_registerModuleImpl(DOME_Context ctx, const char* name, const char* source) { + + ENGINE* engine = (ENGINE*)ctx; + MAP* moduleMap = &(engine->moduleMap); + if (MAP_addModule(moduleMap, name, source)) { + return DOME_RESULT_SUCCESS; + } + return DOME_RESULT_FAILURE; +} + +internal DOME_Result +DOME_registerFnImpl(DOME_Context ctx, const char* moduleName, const char* signature, DOME_ForeignFn method) { + + ENGINE* engine = (ENGINE*)ctx; + MAP* moduleMap = &(engine->moduleMap); + if (MAP_addFunction(moduleMap, moduleName, signature, (WrenForeignMethodFn)method)) { + return DOME_RESULT_SUCCESS; + } + + return DOME_RESULT_FAILURE; +} + +internal DOME_Result +DOME_registerClassImpl(DOME_Context ctx, const char* moduleName, const char* className, DOME_ForeignFn allocate, DOME_FinalizerFn finalize) { + + // TODO: handle null allocate ptr + ENGINE* engine = (ENGINE*)ctx; + MAP* moduleMap = &(engine->moduleMap); + if (MAP_addClass(moduleMap, moduleName, className, (WrenForeignMethodFn)allocate, (WrenFinalizerFn)finalize)) { + return DOME_RESULT_SUCCESS; + } + + return DOME_RESULT_FAILURE; +} + +internal void +DOME_lockModuleImpl(DOME_Context ctx, const char* moduleName) { + ENGINE* engine = (ENGINE*)ctx; + MAP* moduleMap = &(engine->moduleMap); + + MAP_lockModule(moduleMap, moduleName); +} + +internal WrenVM* +DOME_getVM(DOME_Context ctx) { + ENGINE* engine = (ENGINE*)ctx; + return engine->vm; +} + +internal DOME_Context +DOME_getVMContext(WrenVM* vm) { + return wrenGetUserData(vm); +} +internal void +DOME_printLog(DOME_Context ctx, const char* text, ...) { + va_list args; + va_start(args, text); + ENGINE_printLogVariadic(ctx, text, args); + va_end(args); +} + +internal const char* +DOME_getLastError(DOME_Context ctx) { + return PLUGIN_COLLECTION_getErrorReason((ENGINE*)ctx); +} + + +DOME_API_v0 dome_v0 = { + .registerModule = DOME_registerModuleImpl, + .registerFn = DOME_registerFnImpl, + .registerClass = DOME_registerClassImpl, + .lockModule = DOME_lockModuleImpl, + .getContext = DOME_getVMContext, + .getVM = DOME_getVM, + .log = DOME_printLog, + .getLastError = DOME_getLastError +}; diff --git a/src/api/io.c b/src/api/io.c new file mode 100644 index 00000000..4960fc71 --- /dev/null +++ b/src/api/io.c @@ -0,0 +1,11 @@ +internal void* +IO_API_readFile(DOME_Context ctx, const char* path, size_t* lengthPtr) { + ENGINE* engine = (ENGINE*)ctx; + char* message = PLUGIN_COLLECTION_getErrorReason((ENGINE*)ctx); + return ENGINE_readFile(engine, path, lengthPtr, &message); +} + + +IO_API_v0 io_v0 = { + .readFile = IO_API_readFile, +}; diff --git a/src/engine.c b/src/engine.c index b5d249f9..7ac0edc9 100644 --- a/src/engine.c +++ b/src/engine.c @@ -5,59 +5,6 @@ getColorComponents(uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b) { *b = (color & (0xFF << 16)) >> 16; } -internal int -ENGINE_record(void* ptr) { - // Thread: Seperate gif record - ENGINE* engine = ptr; - size_t imageSize = engine->canvas.width * engine->canvas.height; - engine->record.gifPixels = (uint32_t*)malloc(imageSize*4*sizeof(uint8_t)); - size_t scale = GIF_SCALE; - uint32_t* scaledPixels = (uint32_t*)malloc(imageSize*4*sizeof(uint8_t)* scale * scale); - CANVAS canvas = engine->canvas; - - jo_gif_t gif = jo_gif_start(engine->record.gifName, canvas.width * scale, canvas.height * scale, 0, 31); - uint8_t FPS = 30; - double MS_PER_FRAME = ceil(1000.0 / FPS); - double lag = 0; - uint64_t previousTime = SDL_GetPerformanceCounter(); - do { - SDL_Delay(1); - uint64_t currentTime = SDL_GetPerformanceCounter(); - double elapsed = 1000 * (currentTime - previousTime) / (double)SDL_GetPerformanceFrequency(); - previousTime = currentTime; - if(fabs(elapsed - 1.0/120.0) < .0002){ - elapsed = 1.0/120.0; - } - if(fabs(elapsed - 1.0/60.0) < .0002){ - elapsed = 1.0/60.0; - } - if(fabs(elapsed - 1.0/30.0) < .0002){ - elapsed = 1.0/30.0; - } - lag += elapsed; - if (lag >= MS_PER_FRAME) { - if (scale > 1) { - for (size_t j = 0; j < canvas.height * scale; j++) { - for (size_t i = 0; i < canvas.width * scale; i++) { - size_t u = i / scale; - size_t v = j / scale; - int32_t c = ((uint32_t*)engine->record.gifPixels)[v * canvas.width + u]; - scaledPixels[j * canvas.width * scale + i] = c; - } - } - jo_gif_frame(&gif, (uint8_t*)scaledPixels, 4, true); - } else { - jo_gif_frame(&gif, (uint8_t*)engine->record.gifPixels, 3, true); - } - lag -= MS_PER_FRAME; - } - } while(engine->running); - - jo_gif_end(&gif); - free(engine->record.gifPixels); - return 0; -} - internal void ENGINE_openLogFile(ENGINE* engine) { // DOME-2020-02-02-090000.log @@ -129,7 +76,7 @@ ENGINE_writeFile(ENGINE* engine, const char* path, const char* buffer, size_t le } internal char* -ENGINE_readFile(ENGINE* engine, const char* path, size_t* lengthPtr) { +ENGINE_readFile(ENGINE* engine, const char* path, size_t* lengthPtr, char** reason) { char pathBuf[PATH_MAX]; if (strncmp(path, "./", 2) == 0) { @@ -147,8 +94,9 @@ ENGINE_readFile(ENGINE* engine, const char* path, size_t* lengthPtr) { return file; } + char* message = mtar_strerror(err); if (DEBUG_MODE) { - ENGINE_printLog(engine, "Couldn't read %s from bundle: %s. Falling back\n", pathBuf, mtar_strerror(err)); + ENGINE_printLog(engine, "Couldn't read %s from bundle: %s. Falling back\n", pathBuf, message); } } @@ -161,11 +109,14 @@ ENGINE_readFile(ENGINE* engine, const char* path, size_t* lengthPtr) { emscripten_wget(pathBuf, pathBuf); #endif if (!doesFileExist(pathBuf)) { + if (reason != NULL) { + strncpy(*reason, "File doesn't exist", 1024); + } return NULL; } ENGINE_printLog(engine, "Reading from filesystem: %s\n", pathBuf); - return readEntireFile(pathBuf, lengthPtr); + return readEntireFile(pathBuf, lengthPtr, reason); } internal int @@ -209,6 +160,7 @@ ENGINE_setupRenderer(ENGINE* engine, bool vsync) { internal ENGINE* ENGINE_init(ENGINE* engine) { engine->handleText = true; + engine->fused = false; engine->window = NULL; engine->renderer = NULL; engine->texture = NULL; @@ -225,6 +177,7 @@ ENGINE_init(ENGINE* engine) { engine->debug.errorBufMax = 0; engine->debug.errorBuf = NULL; engine->debug.errorBufLen = 0; + engine->debug.errorDialog = true; // Initialise the canvas offset. engine->canvas.pixels = NULL; @@ -364,6 +317,10 @@ ENGINE_free(ENGINE* engine) { SDL_DestroyWindow(engine->window); } + if (engine->mouse.cursor != NULL) { + SDL_FreeCursor(engine->mouse.cursor); + } + if (engine->argv != NULL) { free(engine->argv[1]); free(engine->argv); @@ -391,14 +348,15 @@ ENGINE_pget(ENGINE* engine, int64_t x, int64_t y) { inline internal void ENGINE_pset(ENGINE* engine, int64_t x, int64_t y, uint32_t c) { + CANVAS canvas = engine->canvas; // Account for canvas offset - x += engine->canvas.offsetX; - y += engine->canvas.offsetY; + x += canvas.offsetX; + y += canvas.offsetY; // Draw pixel at (x,y) - int32_t width = engine->canvas.width; - DOME_RECT zone = engine->canvas.clip; + int32_t width = canvas.width; + DOME_RECT zone = canvas.clip; uint8_t newA = ((0xFF000000 & c) >> 24); @@ -406,7 +364,7 @@ ENGINE_pset(ENGINE* engine, int64_t x, int64_t y, uint32_t c) { return; } else if (zone.x <= x && x < zone.x + zone.w && zone.y <= y && y < zone.y + zone.h) { if (newA < 0xFF) { - uint32_t current = ((uint32_t*)(engine->canvas.pixels))[width * y + x]; + uint32_t current = ((uint32_t*)(canvas.pixels))[width * y + x]; double normA = newA / (double)UINT8_MAX; double diffA = 1 - normA; @@ -424,10 +382,47 @@ ENGINE_pset(ENGINE* engine, int64_t x, int64_t y, uint32_t c) { // This is a very hot line, so we use pointer arithmetic for // speed! - *(((uint32_t*)engine->canvas.pixels) + (width * y + x)) = c; + *(((uint32_t*)canvas.pixels) + (width * y + x)) = c; } } +internal void +ENGINE_unsafePsetNoBlend(ENGINE* engine, int64_t x, int64_t y, uint32_t c) { + CANVAS canvas = engine->canvas; + // Draw pixel at (x,y) + // This is a very hot line, so we use pointer arithmetic for + // speed! + *(((uint32_t*)canvas.pixels) + (canvas.width * y + x)) = c; +} + +internal void +ENGINE_unsafePset(ENGINE* engine, int64_t x, int64_t y, uint32_t c) { + CANVAS canvas = engine->canvas; + + // Draw pixel at (x,y) + int32_t width = canvas.width; + uint8_t newA = ((0xFF000000 & c) >> 24); + + uint32_t current = ((uint32_t*)(canvas.pixels))[width * y + x]; + double normA = newA / (double)UINT8_MAX; + double diffA = 1 - normA; + + uint8_t oldR, oldG, oldB, newR, newG, newB; + getColorComponents(current, &oldR, &oldG, &oldB); + getColorComponents(c, &newR, &newG, &newB); + + uint8_t a = 0xFF; + uint8_t r = (diffA * oldR + normA * newR); + uint8_t g = (diffA * oldG + normA * newG); + uint8_t b = (diffA * oldB + normA * newB); + + c = (a << 24) | (b << 16) | (g << 8) | r; + + // This is a very hot line, so we use pointer arithmetic for + // speed! + *(((uint32_t*)canvas.pixels) + (width * y + x)) = c; +} + internal void ENGINE_blitBuffer(ENGINE* engine, int32_t x, int32_t y) { PIXEL_BUFFER buffer = engine->blitBuffer; @@ -882,6 +877,75 @@ ENGINE_ellipse(ENGINE* engine, int64_t x0, int64_t y0, int64_t x1, int64_t y1, u ENGINE_blitBuffer(engine, x0, y0); } +internal void +ENGINE_triangle(ENGINE* engine, float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c) { + ENGINE_line(engine, x0, y0, x1, y1, c, 1); + ENGINE_line(engine, x1, y1, x2, y2, c, 1); + ENGINE_line(engine, x2, y2, x0, y0, c, 1); +} + +internal void +ENGINE_trianglefillFlatBottom(ENGINE* engine, float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c) { + float invslope0 = (x1 - x0) / (y1 - y0); + float invslope1 = (x2 - x0) / (y2 - y0); + float curx0 = x0; + float curx1 = x0; + + for (int scanlineY = y0; scanlineY <= y1; scanlineY++) { + ENGINE_line(engine, curx0, scanlineY, curx1, scanlineY, c, 1); + curx0 += invslope0; + curx1 += invslope1; + } +} + +internal void +ENGINE_trianglefillFlatTop(ENGINE* engine, float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c) { + float invslope0 = (x2 - x0) / (y2 - y0); + float invslope1 = (x2 - x1) / (y2 - y1); + + float curx0 = x2; + float curx1 = x2; + + for (int scanlineY = y2; scanlineY > y0; scanlineY--) { + ENGINE_line(engine, curx0, scanlineY, curx1, scanlineY, c, 1); + curx0 -= invslope0; + curx1 -= invslope1; + } +} + +internal void +ENGINE_trianglefill(ENGINE* engine, float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c) { + if (y1 < y0) { + swap(&x1, &x0); + swap(&y1, &y0); + } + if (y2 < y0) { + swap(&x2, &x0); + swap(&y2, &y0); + } + if (y2 < y1) { + swap(&x2, &x1); + swap(&y2, &y1); + } + + x0 += 0.5; + y0 += 0.5; + x1 += 0.5; + y1 += 0.5; + x2 += 0.5; + y2 += 0.5; + + if (y1 == y2) { + ENGINE_trianglefillFlatBottom(engine, x0, y0, x1, y1, x2, y2, c); + } else if (y0 == y1) { + ENGINE_trianglefillFlatTop(engine, x0, y0, x1, y1, x2, y2, c); + } else { + float x3 = (x0 + ((y1 - y0) / (y2 - y0)) * (x2 - x0)); + ENGINE_trianglefillFlatBottom(engine, x0, y0, x1, y1, x3, y1, c); + ENGINE_trianglefillFlatTop(engine, x1, y1, x3, y1, x2, y2, c); + } +} + internal void ENGINE_rect(ENGINE* engine, int64_t x, int64_t y, int64_t w, int64_t h, uint32_t c) { w = w - 1; @@ -931,6 +995,30 @@ ENGINE_getKeyState(ENGINE* engine, char* keyName) { return state[scancode]; } +internal int32_t +ENGINE_findMouseCursorIndex(ENGINE* engine, const char* cursorName) { + for (int index = 0; index < SDL_NUM_SYSTEM_CURSORS; index++) { + char * name = ENGINE_MOUSE_CURSORS[index]; + if (STRINGS_EQUAL(cursorName, name)) { + return index; + } + } + return -1; +} + +internal void +ENGINE_setMouseCursor(ENGINE* engine, int32_t cursorID) { + SDL_FreeCursor(engine->mouse.cursor); + engine->mouse.cursorID = cursorID; + engine->mouse.cursor = SDL_CreateSystemCursor(engine->mouse.cursorID); + SDL_SetCursor(engine->mouse.cursor); +} + +internal const char* +ENGINE_getMouseCursor(ENGINE* engine) { + return ENGINE_MOUSE_CURSORS[engine->mouse.cursorID]; +} + internal void ENGINE_setMouseRelative(ENGINE* engine, bool relative) { engine->mouse.relative = relative; @@ -1041,9 +1129,6 @@ ENGINE_drawDebug(ENGINE* engine) { internal bool ENGINE_canvasResize(ENGINE* engine, uint32_t newWidth, uint32_t newHeight, uint32_t color) { - if (engine->initialized && engine->record.makeGif) { - return true; - } if (engine->canvas.width == newWidth && engine->canvas.height == newHeight) { return true; } @@ -1084,9 +1169,11 @@ internal void ENGINE_reportError(ENGINE* engine) { if (engine->debug.errorBuf != NULL) { ENGINE_printLog(engine, engine->debug.errorBuf); - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, + if (engine->debug.errorDialog) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DOME - Error", engine->debug.errorBuf, NULL); + } } } diff --git a/src/engine.h b/src/engine.h index 1ecda586..8f283628 100644 --- a/src/engine.h +++ b/src/engine.h @@ -1,9 +1,3 @@ -// Forward-declaring some methods for interacting with the AudioEngine -// for managing memory and initialization -struct AUDIO_ENGINE_t; -internal struct AUDIO_ENGINE_t* AUDIO_ENGINE_init(void); -internal void AUDIO_ENGINE_free(struct AUDIO_ENGINE_t*); - typedef struct { int64_t x; int64_t y; @@ -11,12 +5,29 @@ typedef struct { int64_t h; } DOME_RECT; +char * const ENGINE_MOUSE_CURSORS[] = { + [SDL_SYSTEM_CURSOR_ARROW] = "arrow", + [SDL_SYSTEM_CURSOR_IBEAM] = "ibeam", + [SDL_SYSTEM_CURSOR_WAIT] = "wait", + [SDL_SYSTEM_CURSOR_CROSSHAIR] = "crosshair", + [SDL_SYSTEM_CURSOR_WAITARROW] = "waitarrow", + [SDL_SYSTEM_CURSOR_SIZENWSE] = "sizenwse", + [SDL_SYSTEM_CURSOR_SIZENESW] = "sizenesw", + [SDL_SYSTEM_CURSOR_SIZEWE] = "sizewe", + [SDL_SYSTEM_CURSOR_SIZENS] = "sizens", + [SDL_SYSTEM_CURSOR_SIZEALL] = "sizeall", + [SDL_SYSTEM_CURSOR_NO] = "no", + [SDL_SYSTEM_CURSOR_HAND] = "hand" +}; + typedef struct { bool relative; int x; int y; int scrollX; int scrollY; + int cursorID; + SDL_Cursor* cursor; } ENGINE_MOUSE_STATE; typedef struct { @@ -27,6 +38,7 @@ typedef struct { size_t errorBufLen; size_t errorBufMax; char* errorBuf; + bool errorDialog; } ENGINE_DEBUG; typedef struct { @@ -37,8 +49,8 @@ typedef struct { } ENGINE_RECORDER; typedef struct { - size_t height; - size_t width; + int32_t width; + int32_t height; uint32_t* pixels; } PIXEL_BUFFER; @@ -57,6 +69,7 @@ typedef struct ENGINE_t { SDL_Renderer *renderer; SDL_Texture *texture; SDL_Rect viewport; + WrenVM* vm; CANVAS canvas; PIXEL_BUFFER blitBuffer; ABC_FIFO fifo; @@ -76,6 +89,7 @@ typedef struct ENGINE_t { int exit_status; struct AUDIO_ENGINE_t* audioEngine; bool initialized; + bool fused; bool debugEnabled; bool vsyncEnabled; ENGINE_DEBUG debug; @@ -103,4 +117,3 @@ typedef enum { } ENGINE_WRITE_RESULT; global_variable uint32_t ENGINE_EVENT_TYPE; - diff --git a/src/util/font.c b/src/font/font.c similarity index 100% rename from src/util/font.c rename to src/font/font.c diff --git a/src/util/font8x8.h b/src/font/font8x8.h similarity index 100% rename from src/util/font8x8.h rename to src/font/font8x8.h diff --git a/src/util/font8x8_basic.h b/src/font/font8x8_basic.h similarity index 100% rename from src/util/font8x8_basic.h rename to src/font/font8x8_basic.h diff --git a/src/util/font8x8_block.h b/src/font/font8x8_block.h similarity index 100% rename from src/util/font8x8_block.h rename to src/font/font8x8_block.h diff --git a/src/util/font8x8_box.h b/src/font/font8x8_box.h similarity index 100% rename from src/util/font8x8_box.h rename to src/font/font8x8_box.h diff --git a/src/util/font8x8_control.h b/src/font/font8x8_control.h similarity index 100% rename from src/util/font8x8_control.h rename to src/font/font8x8_control.h diff --git a/src/util/font8x8_ext_latin.h b/src/font/font8x8_ext_latin.h similarity index 100% rename from src/util/font8x8_ext_latin.h rename to src/font/font8x8_ext_latin.h diff --git a/src/util/font8x8_greek.h b/src/font/font8x8_greek.h similarity index 100% rename from src/util/font8x8_greek.h rename to src/font/font8x8_greek.h diff --git a/src/util/font8x8_hiragana.h b/src/font/font8x8_hiragana.h similarity index 100% rename from src/util/font8x8_hiragana.h rename to src/font/font8x8_hiragana.h diff --git a/src/util/font8x8_latin.h b/src/font/font8x8_latin.h similarity index 100% rename from src/util/font8x8_latin.h rename to src/font/font8x8_latin.h diff --git a/src/util/font8x8_misc.h b/src/font/font8x8_misc.h similarity index 100% rename from src/util/font8x8_misc.h rename to src/font/font8x8_misc.h diff --git a/src/util/font8x8_sga.h b/src/font/font8x8_sga.h similarity index 100% rename from src/util/font8x8_sga.h rename to src/font/font8x8_sga.h diff --git a/src/game.c b/src/game.c new file mode 100644 index 00000000..6bf2370b --- /dev/null +++ b/src/game.c @@ -0,0 +1,439 @@ +typedef struct { + ENGINE* engine; + WrenVM* vm; + WrenHandle* gameClass; + WrenHandle* updateMethod; + WrenHandle* drawMethod; + double MS_PER_FRAME; + double FPS; + double lag; + uint64_t previousTime; + uint64_t currentTime; + double elapsed; + bool windowBlurred; + uint8_t attempts; + bool tickRender; +} LOOP_STATE; + +internal void +DOME_release(LOOP_STATE* state) { + WrenVM* vm = state->vm; + + if (state->drawMethod != NULL) { + wrenReleaseHandle(vm, state->drawMethod); + } + + if (state->updateMethod != NULL) { + wrenReleaseHandle(vm, state->updateMethod); + } + + if (state->gameClass != NULL) { + wrenReleaseHandle(vm, state->gameClass); + } +} + +internal int +DOME_processInput(LOOP_STATE* state) { + WrenInterpretResult interpreterResult; + ENGINE* engine = state->engine; + WrenVM* vm = state->vm; + engine->mouse.scrollX = 0; + engine->mouse.scrollY = 0; + SDL_Event event; + INPUT_clearText(vm); + while(SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + engine->running = false; + break; + case SDL_WINDOWEVENT: + { + if (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + SDL_RenderGetViewport(engine->renderer, &(engine->viewport)); + break; + } + if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { +#ifdef __EMSCRIPTEN__ + AUDIO_ENGINE_pause(engine->audioEngine); +#endif + state->windowBlurred = true; + } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { +#ifdef __EMSCRIPTEN__ + AUDIO_ENGINE_resume(engine->audioEngine); +#endif + ENGINE_updateTextRegion(engine); + state->windowBlurred = false; + } + } break; + case SDL_KEYDOWN: + case SDL_KEYUP: + { + SDL_Keycode keyCode = event.key.keysym.sym; + if (keyCode == SDLK_F3 && event.key.state == SDL_PRESSED && event.key.repeat == 0) { + engine->debugEnabled = !engine->debugEnabled; + } else if (keyCode == SDLK_F2 && event.key.state == SDL_PRESSED && event.key.repeat == 0) { + ENGINE_takeScreenshot(engine); + } else if (event.key.repeat == 0) { + char* buttonName = strToLower((char*)SDL_GetKeyName(keyCode)); + interpreterResult = INPUT_update(vm, DOME_INPUT_KEYBOARD, buttonName, event.key.state == SDL_PRESSED); + free(buttonName); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + } + } break; + case SDL_TEXTEDITING: + { + if (utf8len(event.edit.text) > 0) { + INPUT_setCompositionText(vm, event.edit.text, event.edit.start, event.edit.length); + } + } break; + case SDL_TEXTINPUT: + { + if (utf8len(event.text.text) > 0) { + INPUT_addText(vm, event.text.text); + } + } break; + + case SDL_CONTROLLERDEVICEADDED: + { + GAMEPAD_eventAdded(vm, event.cdevice.which); + } break; + case SDL_CONTROLLERDEVICEREMOVED: + { + GAMEPAD_eventRemoved(vm, event.cdevice.which); + } break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + { + SDL_ControllerButtonEvent cbutton = event.cbutton; + const char* buttonName = GAMEPAD_stringFromButton(cbutton.button); + interpreterResult = GAMEPAD_eventButtonPressed(vm, cbutton.which, buttonName, cbutton.state == SDL_PRESSED); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + } break; + case SDL_MOUSEWHEEL: + { + int dir = event.wheel.direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1; + engine->mouse.scrollX += event.wheel.x * dir; + // Down should be positive to match the direction of rendering. + engine->mouse.scrollY += event.wheel.y * -dir; + } break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + char* buttonName; + switch (event.button.button) { + case SDL_BUTTON_LEFT: buttonName = "left"; break; + case SDL_BUTTON_MIDDLE: buttonName = "middle"; break; + case SDL_BUTTON_RIGHT: buttonName = "right"; break; + case SDL_BUTTON_X1: buttonName = "x1"; break; + default: + case SDL_BUTTON_X2: buttonName = "x2"; break; + } + bool state = event.button.state == SDL_PRESSED; + interpreterResult = INPUT_update(vm, DOME_INPUT_MOUSE, buttonName, state); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + } break; + case SDL_USEREVENT: + { + ENGINE_printLog(engine, "Event code %i\n", event.user.code); + if (event.user.code == EVENT_LOAD_FILE) { + FILESYSTEM_loadEventComplete(&event); + } + } + } + } + if (inputCaptured) { + interpreterResult = INPUT_commit(vm); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; +} + +internal int +DOME_render(LOOP_STATE* state) { + if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_DRAW) != DOME_RESULT_SUCCESS) { + return EXIT_FAILURE; + }; + WrenInterpretResult interpreterResult; + wrenEnsureSlots(state->vm, 8); + wrenSetSlotHandle(state->vm, 0, state->gameClass); + wrenSetSlotDouble(state->vm, 1, ((double)state->lag / state->MS_PER_FRAME)); + interpreterResult = wrenCall(state->vm, state->drawMethod); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_DRAW) != DOME_RESULT_SUCCESS) { + return EXIT_FAILURE; + }; + + return EXIT_SUCCESS; +} + +internal void +DOME_flip(LOOP_STATE* state) { + state->engine->debug.elapsed = state->elapsed; + if (state->engine->debugEnabled) { + ENGINE_drawDebug(state->engine); + } + // Flip Buffer to Screen + SDL_UpdateTexture(state->engine->texture, 0, state->engine->canvas.pixels, state->engine->canvas.width * 4); + // clear screen + SDL_RenderClear(state->engine->renderer); + SDL_RenderCopy(state->engine->renderer, state->engine->texture, NULL, NULL); + SDL_RenderPresent(state->engine->renderer); +} + +internal int +DOME_update(LOOP_STATE* state) { + WrenInterpretResult interpreterResult; + + if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_UPDATE) != DOME_RESULT_SUCCESS) { + return EXIT_FAILURE; + }; + + wrenEnsureSlots(state->vm, 8); + wrenSetSlotHandle(state->vm, 0, state->gameClass); + interpreterResult = wrenCall(state->vm, state->updateMethod); + if (interpreterResult != WREN_RESULT_SUCCESS) { + return EXIT_FAILURE; + } + if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_UPDATE) != DOME_RESULT_SUCCESS) { + return EXIT_FAILURE; + }; + // updateAudio() + AUDIO_ENGINE_update(state->engine->audioEngine, state->vm); + return EXIT_SUCCESS; +} + +void DOME_loop(void* data) { + LOOP_STATE loop = *((LOOP_STATE*)data); + loop.currentTime = SDL_GetPerformanceCounter(); + loop.elapsed = 1000 * (loop.currentTime - loop.previousTime) / (double) SDL_GetPerformanceFrequency(); + loop.previousTime = loop.currentTime; + loop.lag += loop.elapsed; + + if (loop.lag >= loop.MS_PER_FRAME) { + DOME_processInput(&loop); + if (loop.windowBlurred) { + loop.lag = 0; + loop.tickRender = true; + return; + } + DOME_update(&loop); + if (loop.tickRender) { + DOME_render(&loop); + DOME_flip(&loop); + } + loop.tickRender = !loop.tickRender; + loop.lag = mid(0, loop.lag - loop.MS_PER_FRAME, loop.MS_PER_FRAME); + } + *((LOOP_STATE*)data) = loop; +} + +int DOME_begin(ENGINE* engine, char* entryPath) { + int result = EXIT_SUCCESS; + WrenVM* vm = NULL; + INIT_TO_ZERO(LOOP_STATE, loop); + loop.FPS = 60; + loop.MS_PER_FRAME = ceil(1000.0 / loop.FPS); + loop.engine = engine; + // The basepath is incorporated later, so we pass the basename version to this method. + size_t gameFileLength; + char* gameFile; + gameFile = ENGINE_readFile(engine, entryPath, &gameFileLength, NULL); + if (gameFile == NULL) { + if (engine->tar != NULL) { + ENGINE_printLog(engine, "Error: Could not load %s in bundle.\n", entryPath); + } else { + ENGINE_printLog(engine, "Error: Could not load %s.\n", engine->argv[1]); + } + result = EXIT_FAILURE; + goto cleanup; + } + + result = ENGINE_start(engine); + if (result == EXIT_FAILURE) { + goto cleanup; + } + + // Configure Wren VM + vm = VM_create(engine); + WrenInterpretResult interpreterResult; + loop.vm = vm; + + // Load user game file + WrenHandle* initMethod = NULL; + + interpreterResult = wrenInterpret(vm, "main", gameFile); + free(gameFile); + if (interpreterResult != WREN_RESULT_SUCCESS) { + result = EXIT_FAILURE; + goto vm_cleanup; + } + // Load the class into slot 0. + + + wrenEnsureSlots(vm, 3); + initMethod = wrenMakeCallHandle(vm, "init()"); + wrenGetVariable(vm, "main", "Game", 0); + loop.gameClass = wrenGetSlotHandle(vm, 0); + loop.updateMethod = wrenMakeCallHandle(vm, "update()"); + loop.drawMethod = wrenMakeCallHandle(vm, "draw(_)"); + + SDL_SetRenderDrawColor(engine->renderer, 0x00, 0x00, 0x00, 0xFF); + + // Initiate game loop + + wrenSetSlotHandle(vm, 0, loop.gameClass); + interpreterResult = wrenCall(vm, initMethod); + if (interpreterResult != WREN_RESULT_SUCCESS) { + result = EXIT_FAILURE; + goto vm_cleanup; + } + // Release this handle if it finished successfully + wrenReleaseHandle(vm, initMethod); + initMethod = NULL; + engine->initialized = true; + + SDL_SetWindowPosition(engine->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_ShowWindow(engine->window); + + loop.lag = loop.MS_PER_FRAME; + result = DOME_processInput(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + loop.windowBlurred = false; + loop.previousTime = SDL_GetPerformanceCounter(); +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(DOME_loop, &loop, 0, true); +#endif + while (engine->running) { + + // processInput() + if (loop.windowBlurred) { + result = DOME_processInput(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + } + + loop.currentTime = SDL_GetPerformanceCounter(); + loop.elapsed = 1000 * (loop.currentTime - loop.previousTime) / (double) SDL_GetPerformanceFrequency(); + loop.previousTime = loop.currentTime; + + + // If we aren't focused, we skip the update loop and let the CPU sleep + // to be good citizens + if (loop.windowBlurred) { + SDL_Delay(50); + continue; + } + + if(fabs(loop.elapsed - 1.0/120.0) < .0002){ + loop.elapsed = 1.0/120.0; + } + if(fabs(loop.elapsed - 1.0/60.0) < .0002){ + loop.elapsed = 1.0/60.0; + } + if(fabs(loop.elapsed - 1.0/30.0) < .0002){ + loop.elapsed = 1.0/30.0; + } + loop.lag += loop.elapsed; + + if (engine->lockstep) { + if (loop.lag >= loop.MS_PER_FRAME) { + result = DOME_processInput(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + result = DOME_update(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + result = DOME_render(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + loop.lag = mid(0, loop.lag - loop.MS_PER_FRAME, loop.MS_PER_FRAME); + DOME_flip(&loop); + } + } else { + loop.attempts = 5; + while (loop.attempts > 0 && loop.lag >= loop.MS_PER_FRAME) { + loop.attempts--; + + result = DOME_processInput(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + // update() + result = DOME_update(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + loop.lag -= loop.MS_PER_FRAME; + } + // render(); + result = DOME_render(&loop); + if (result != EXIT_SUCCESS) { + goto vm_cleanup; + } + if (loop.attempts == 0) { + loop.lag = 0; + } + DOME_flip(&loop); + } + + + if (!engine->vsyncEnabled) { + SDL_Delay(1); + } + } + +vm_cleanup: + if (PLUGIN_COLLECTION_runHook(engine, DOME_PLUGIN_HOOK_SHUTDOWN) != DOME_RESULT_SUCCESS) { + return EXIT_FAILURE; + }; + // Finish processing async threads so we can release resources + ENGINE_finishAsync(engine); + SDL_Event event; + while(SDL_PollEvent(&event)) { + if (event.type == SDL_USEREVENT) { + if (event.user.code == EVENT_LOAD_FILE) { + FILESYSTEM_loadEventComplete(&event); + } + } + } + + // Free resources + ENGINE_reportError(engine); + + if (initMethod != NULL) { + wrenReleaseHandle(vm, initMethod); + } + + DOME_release(&loop); + + if (bufferClass != NULL) { + wrenReleaseHandle(vm, bufferClass); + } + + INPUT_release(vm); + + AUDIO_ENGINE_halt(engine->audioEngine); + AUDIO_ENGINE_releaseHandles(engine->audioEngine, vm); + + VM_free(vm); + result = engine->exit_status; +cleanup: + return result; +} diff --git a/src/io.c b/src/io.c index ebf7c735..ca022887 100644 --- a/src/io.c +++ b/src/io.c @@ -177,25 +177,50 @@ writeEntireFile(const char* path, const char* data, size_t length) { } internal char* -readEntireFile(char* path, size_t* lengthPtr) { +readEntireFile(char* path, size_t* lengthPtr, char** error) { FILE* file = fopen(path, "rb"); if (file == NULL) { + if (error != NULL) { + strncpy(*error, strerror(errno), 1024); + } return NULL; } char* source = NULL; if (fseek(file, 0L, SEEK_END) == 0) { /* Get the size of the file. */ long bufsize = ftell(file); + if (bufsize == -1) { + if (error != NULL) { + strncpy(*error, strerror(errno), 1024); + } + return NULL; + } + /* Allocate our buffer to that size. */ source = malloc(sizeof(char) * (bufsize + 1)); + if (source == NULL) { + if (error != NULL) { + strncpy(*error, strerror(errno), 1024); + } + return NULL; + } /* Go back to the start of the file. */ - if (fseek(file, 0L, SEEK_SET) != 0) { /* Error */ } + if (fseek(file, 0L, SEEK_SET) != 0) { + if (error != NULL) { + strncpy(*error, strerror(errno), 1024); + } + return NULL; + } /* Read the entire file into memory. */ size_t newLen = fread(source, sizeof(char), bufsize, file); if (ferror(file) != 0) { - perror("Error reading file"); + if (error != NULL) { + strncpy(*error, strerror(errno), 1024); + } + clearerr(file); + return NULL; } else { if (lengthPtr != NULL) { *lengthPtr = newLen; diff --git a/src/main.c b/src/main.c index 8d090852..ff46f08d 100644 --- a/src/main.c +++ b/src/main.c @@ -27,7 +27,7 @@ #include #include #ifndef M_PI - #define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #endif #include @@ -84,7 +84,6 @@ global_variable bool DEBUG_MODE = true; global_variable bool DEBUG_MODE = false; #endif global_variable size_t AUDIO_BUFFER_SIZE = 2048; -global_variable size_t GIF_SCALE = 1; @@ -96,19 +95,33 @@ global_variable size_t GIF_SCALE = 1; #include "plugin.h" #include "engine.h" -#include "util/font8x8.h" +#include "font/font8x8.h" #include "io.c" #include "audio/engine.h" #include "audio/hashmap.c" #include "audio/engine.c" #include "audio/channel.c" -#include "audio/api.c" + #include "debug.c" #include "engine.c" + +#include "api/io.c" +#include "api/audio.c" +#include "api/bitmap.c" +#include "api/canvas.c" +#include "api/core.c" #include "plugin.c" + +#ifndef __EMSCRIPTEN__ +#include "tools/help.c" +#include "tools/fuse.c" +#include "tools/embed.c" +#include "tools/nest.c" +#endif + #include "modules/dome.c" #include "modules/font.c" #include "modules/io.c" @@ -120,234 +133,10 @@ global_variable size_t GIF_SCALE = 1; #include "modules/platform.c" #include "modules/random.c" #include "modules/plugin.c" -#include "util/wrenembed.c" - - // Comes last to register modules #include "vm.c" +#include "game.c" -typedef struct { - ENGINE* engine; - WrenVM* vm; - WrenHandle* gameClass; - WrenHandle* updateMethod; - WrenHandle* drawMethod; - double MS_PER_FRAME; - double FPS; - double lag; - uint64_t previousTime; - uint64_t currentTime; - double elapsed; - bool windowBlurred; - uint8_t attempts; - bool tickRender; -} LOOP_STATE; - -internal void -LOOP_release(LOOP_STATE* state) { - WrenVM* vm = state->vm; - - if (state->drawMethod != NULL) { - wrenReleaseHandle(vm, state->drawMethod); - } - - if (state->updateMethod != NULL) { - wrenReleaseHandle(vm, state->updateMethod); - } - - if (state->gameClass != NULL) { - wrenReleaseHandle(vm, state->gameClass); - } -} - -internal int -LOOP_processInput(LOOP_STATE* state) { - WrenInterpretResult interpreterResult; - ENGINE* engine = state->engine; - WrenVM* vm = state->vm; - engine->mouse.scrollX = 0; - engine->mouse.scrollY = 0; - SDL_Event event; - INPUT_clearText(vm); - while(SDL_PollEvent(&event)) { - switch (event.type) - { - case SDL_QUIT: - engine->running = false; - break; - case SDL_WINDOWEVENT: - { - if (event.window.event == SDL_WINDOWEVENT_RESIZED || - event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { - SDL_RenderGetViewport(engine->renderer, &(engine->viewport)); - break; - } - if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { -#ifdef __EMSCRIPTEN__ - AUDIO_ENGINE_pause(engine->audioEngine); -#endif - state->windowBlurred = true; - } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { -#ifdef __EMSCRIPTEN__ - AUDIO_ENGINE_resume(engine->audioEngine); -#endif - ENGINE_updateTextRegion(engine); - state->windowBlurred = false; - } - } break; - case SDL_KEYDOWN: - case SDL_KEYUP: - { - SDL_Keycode keyCode = event.key.keysym.sym; - if (keyCode == SDLK_F3 && event.key.state == SDL_PRESSED && event.key.repeat == 0) { - engine->debugEnabled = !engine->debugEnabled; - } else if (keyCode == SDLK_F2 && event.key.state == SDL_PRESSED && event.key.repeat == 0) { - ENGINE_takeScreenshot(engine); - } else if (event.key.repeat == 0) { - char* buttonName = strToLower((char*)SDL_GetKeyName(keyCode)); - interpreterResult = INPUT_update(vm, DOME_INPUT_KEYBOARD, buttonName, event.key.state == SDL_PRESSED); - free(buttonName); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - } - } break; - case SDL_TEXTEDITING: - { - if (utf8len(event.edit.text) > 0) { - INPUT_setCompositionText(vm, event.edit.text, event.edit.start, event.edit.length); - } - } break; - case SDL_TEXTINPUT: - { - if (utf8len(event.text.text) > 0) { - INPUT_addText(vm, event.text.text); - } - } break; - - case SDL_CONTROLLERDEVICEADDED: - { - GAMEPAD_eventAdded(vm, event.cdevice.which); - } break; - case SDL_CONTROLLERDEVICEREMOVED: - { - GAMEPAD_eventRemoved(vm, event.cdevice.which); - } break; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - { - SDL_ControllerButtonEvent cbutton = event.cbutton; - const char* buttonName = GAMEPAD_stringFromButton(cbutton.button); - interpreterResult = GAMEPAD_eventButtonPressed(vm, cbutton.which, buttonName, cbutton.state == SDL_PRESSED); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - } break; - case SDL_MOUSEWHEEL: - { - int dir = event.wheel.direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1; - engine->mouse.scrollX += event.wheel.x * dir; - // Down should be positive to match the direction of rendering. - engine->mouse.scrollY += event.wheel.y * -dir; - } break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - { - char* buttonName; - switch (event.button.button) { - case SDL_BUTTON_LEFT: buttonName = "left"; break; - case SDL_BUTTON_MIDDLE: buttonName = "middle"; break; - case SDL_BUTTON_RIGHT: buttonName = "right"; break; - case SDL_BUTTON_X1: buttonName = "x1"; break; - default: - case SDL_BUTTON_X2: buttonName = "x2"; break; - } - bool state = event.button.state == SDL_PRESSED; - interpreterResult = INPUT_update(vm, DOME_INPUT_MOUSE, buttonName, state); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - } break; - case SDL_USEREVENT: - { - ENGINE_printLog(engine, "Event code %i\n", event.user.code); - if (event.user.code == EVENT_LOAD_FILE) { - FILESYSTEM_loadEventComplete(&event); - } - } - } - } - if (inputCaptured) { - interpreterResult = INPUT_commit(vm); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - } - return EXIT_SUCCESS; -} - -internal int -LOOP_render(LOOP_STATE* state) { - if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_DRAW) != DOME_RESULT_SUCCESS) { - return EXIT_FAILURE; - }; - WrenInterpretResult interpreterResult; - wrenEnsureSlots(state->vm, 8); - wrenSetSlotHandle(state->vm, 0, state->gameClass); - wrenSetSlotDouble(state->vm, 1, ((double)state->lag / state->MS_PER_FRAME)); - interpreterResult = wrenCall(state->vm, state->drawMethod); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_DRAW) != DOME_RESULT_SUCCESS) { - return EXIT_FAILURE; - }; - - - return EXIT_SUCCESS; -} - -internal void -LOOP_flip(LOOP_STATE* state) { - - state->engine->debug.elapsed = state->elapsed; - if (state->engine->debugEnabled) { - ENGINE_drawDebug(state->engine); - } - // Flip Buffer to Screen - SDL_UpdateTexture(state->engine->texture, 0, state->engine->canvas.pixels, state->engine->canvas.width * 4); - // Flip buffer for recording - if (state->engine->record.makeGif) { - size_t imageSize = state->engine->canvas.width * state->engine->canvas.height * 4; - memcpy(state->engine->record.gifPixels, state->engine->canvas.pixels, imageSize); - } - // clear screen - SDL_RenderClear(state->engine->renderer); - SDL_RenderCopy(state->engine->renderer, state->engine->texture, NULL, NULL); - SDL_RenderPresent(state->engine->renderer); -} - -internal int -LOOP_update(LOOP_STATE* state) { - WrenInterpretResult interpreterResult; - - if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_UPDATE) != DOME_RESULT_SUCCESS) { - return EXIT_FAILURE; - }; - - wrenEnsureSlots(state->vm, 8); - wrenSetSlotHandle(state->vm, 0, state->gameClass); - interpreterResult = wrenCall(state->vm, state->updateMethod); - if (interpreterResult != WREN_RESULT_SUCCESS) { - return EXIT_FAILURE; - } - if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_UPDATE) != DOME_RESULT_SUCCESS) { - return EXIT_FAILURE; - }; - // updateAudio() - AUDIO_ENGINE_update(state->engine->audioEngine, state->vm); - return EXIT_SUCCESS; -} internal void printTitle(ENGINE* engine) { @@ -374,432 +163,239 @@ internal void printUsage(ENGINE* engine) { ENGINE_printLog(engine, "\nUsage: \n"); - ENGINE_printLog(engine, " dome [options]\n"); - ENGINE_printLog(engine, " dome [options] [--] entry_path [arguments]\n"); - ENGINE_printLog(engine, " dome -e | --embed sourceFile [moduleName] [destinationFile]\n"); + ENGINE_printLog(engine, " dome [options] []\n"); + ENGINE_printLog(engine, " dome [options] [--] []...\n"); ENGINE_printLog(engine, " dome -h | --help\n"); ENGINE_printLog(engine, " dome -v | --version\n"); + ENGINE_printLog(engine, "\nAvailable Commands: \n"); + ENGINE_printLog(engine, " embed Converts a Wren source file to a C include file for plugin development.\n"); + ENGINE_printLog(engine, " fuse Merges a bundle with DOME to make a standalone file.\n"); + ENGINE_printLog(engine, " help Displays information on how to use each command.\n"); + ENGINE_printLog(engine, " nest Bundle a project into a single file.\n"); ENGINE_printLog(engine, "\nOptions: \n"); ENGINE_printLog(engine, " -b --buffer= Set the audio buffer size (default: 11)\n"); #ifdef __MINGW32__ ENGINE_printLog(engine, " -c --console Opens a console window for development.\n"); #endif ENGINE_printLog(engine, " -d --debug Enables debug mode.\n"); - ENGINE_printLog(engine, " -e --embed Converts a Wren source file to a C include file.\n"); ENGINE_printLog(engine, " -h --help Show this screen.\n"); - ENGINE_printLog(engine, " -r --record= Record video to .\n"); ENGINE_printLog(engine, " -v --version Show version.\n"); } -void DOME_loop(void* data) { - LOOP_STATE loop = *((LOOP_STATE*)data); - loop.currentTime = SDL_GetPerformanceCounter(); - loop.elapsed = 1000 * (loop.currentTime - loop.previousTime) / (double) SDL_GetPerformanceFrequency(); - loop.previousTime = loop.currentTime; - loop.lag += loop.elapsed; - - if (loop.lag >= loop.MS_PER_FRAME) { - LOOP_processInput(&loop); - if (loop.windowBlurred) { - loop.lag = 0; - loop.tickRender = true; - return; - } - LOOP_update(&loop); - if (loop.tickRender) { - LOOP_render(&loop); - LOOP_flip(&loop); - } - loop.tickRender = !loop.tickRender; - loop.lag = mid(0, loop.lag - loop.MS_PER_FRAME, loop.MS_PER_FRAME); - } - *((LOOP_STATE*)data) = loop; -} - -int main(int argc, char* args[]) -{ - // configuring the buffer has to be first - - setbuf(stdout, NULL); - setvbuf(stdout, NULL, _IONBF, 0); - setbuf(stderr, NULL); - setvbuf(stderr, NULL, _IONBF, 0); - - int result = EXIT_SUCCESS; - WrenVM* vm = NULL; - size_t gameFileLength; - char* gameFile; - INIT_TO_ZERO(ENGINE, engine); -#ifdef __EMSCRIPTEN__ - emscripten_wget("game.egg", "game.egg"); -#endif - engine.record.gifName = "test.gif"; - engine.record.makeGif = false; - INIT_TO_ZERO(LOOP_STATE, loop); - loop.FPS = 60; - loop.MS_PER_FRAME = ceil(1000.0 / loop.FPS); - - ENGINE_init(&engine); - loop.engine = &engine; - - struct optparse_long longopts[] = { - {"buffer", 'b', OPTPARSE_REQUIRED}, - #ifdef __MINGW32__ - {"console", 'c', OPTPARSE_NONE}, - #endif - {"debug", 'd', OPTPARSE_NONE}, - {"embed", 'e', OPTPARSE_NONE}, - {"help", 'h', OPTPARSE_NONE}, - {"record", 'r', OPTPARSE_OPTIONAL}, - {"scale", 's', OPTPARSE_REQUIRED}, - {"version", 'v', OPTPARSE_NONE}, - {0} - }; - - int option; - struct optparse options; - optparse_init(&options, args); - while ((option = optparse_long(&options, longopts, NULL)) != -1) { - switch (option) { - case 's': - { - int scale = atoi(options.optarg); - if (scale <= 0) { - // If it wasn't valid, set to a meaningful default. - GIF_SCALE = 1; - } - GIF_SCALE = scale; - } break; - case 'b': - { - int shift = atoi(options.optarg); - if (shift == 0) { - // If it wasn't valid, set to a meaningful default. - AUDIO_BUFFER_SIZE = 2048; - } - AUDIO_BUFFER_SIZE = 1 << shift; - } break; -#ifdef __MINGW32__ - case 'c': { - AllocConsole(); - freopen("CONIN$", "r", stdin); - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); - } break; -#endif - case 'd': - DEBUG_MODE = true; - ENGINE_printLog(&engine, "Debug Mode enabled\n"); - break; - case 'e': - WRENEMBED_encodeAndDumpInDOME(argc, args); - goto cleanup; - case 'h': - printTitle(&engine); - printUsage(&engine); - goto cleanup; - case 'r': - engine.record.makeGif = true; - if (options.optarg != NULL) { - engine.record.gifName = options.optarg; - } else { - engine.record.gifName = "dome.gif"; - } - ENGINE_printLog(&engine, "GIF Recording is enabled: Saving to %s\n", engine.record.gifName); - break; - case 'v': - printTitle(&engine); - printVersion(&engine); - goto cleanup; - case '?': - fprintf(stderr, "%s: %s\n", args[0], options.errmsg); - result = EXIT_FAILURE; - goto cleanup; - } - } - - { - char* defaultEggName = "game.egg"; - char* mainFileName = "main.wren"; - - char* base = BASEPATH_get(); - - char pathBuf[PATH_MAX]; - char* fileName = NULL; - - // Get non-option args list - engine.argv = calloc(max(2, argc), sizeof(char*)); - engine.argv[0] = args[0]; - engine.argv[1] = NULL; - int domeArgCount = 1; - char* otherArg = NULL; - while ((otherArg = optparse_arg(&options))) { - engine.argv[domeArgCount] = otherArg; - domeArgCount++; - } - - bool autoResolve = (domeArgCount == 1); - - domeArgCount = max(2, domeArgCount); - engine.argv = realloc(engine.argv, sizeof(char*) * domeArgCount); - engine.argc = domeArgCount; - - char* arg = NULL; - if (domeArgCount > 1) { - arg = engine.argv[1]; - } - - // Get establish the path components: filename(?) and basepath. - if (arg != NULL) { - strcpy(pathBuf, base); - strcat(pathBuf, arg); - if (isDirectory(pathBuf)) { - BASEPATH_set(pathBuf); +char* resolveEntryPath(ENGINE* engine, char* entryArgument, bool autoResolve) { + char* defaultEggName = "game.egg"; + char* mainFileName = "main.wren"; + char* finalFileName = NULL; + char* entryPath = malloc(sizeof(char) * PATH_MAX); + char* base = BASEPATH_get(); + bool resolved = false; + + if (engine->fused) { + strcpy(entryPath, mainFileName); + } else { + if (entryArgument != NULL) { + // Set the basepath according to the incoming argument + strcpy(entryPath, base); + strcat(entryPath, entryArgument); + if (isDirectory(entryPath)) { autoResolve = true; + BASEPATH_set(entryPath); } else { - char* dirc = strdup(pathBuf); - char* basec = strdup(pathBuf); - // This sets the filename used. - fileName = strdup(basename(dirc)); - BASEPATH_set(dirname(basec)); - free(dirc); - free(basec); + autoResolve = false; + char* directory = dirname(strdup(entryPath)); + finalFileName = basename(strdup(entryPath)); + BASEPATH_set(directory); + free(directory); } base = BASEPATH_get(); + chdir(base); } - // If a filename is given in the path, use it, or assume its 'game.egg' - strcpy(pathBuf, base); - strcat(pathBuf, !autoResolve ? fileName : defaultEggName); - chdir(base); + if (autoResolve) { + strcpy(entryPath, base); + strcat(entryPath, defaultEggName); +#ifdef __EMSCRIPTEN__ + emscripten_wget("game.egg", "game.egg"); +#endif + } - if (doesFileExist(pathBuf)) { - // the current path exists, let's see if it's a TAR file. - engine.tar = malloc(sizeof(mtar_t)); - int tarResult = mtar_open(engine.tar, pathBuf, "r"); + if (doesFileExist(entryPath)) { + mtar_t* tar = malloc(sizeof(mtar_t)); + int tarResult = mtar_open(tar, entryPath, "r"); if (tarResult == MTAR_ESUCCESS) { - ENGINE_printLog(&engine, "Loading bundle %s\n", pathBuf); - engine.argv[1] = strdup(pathBuf); + engine->tar = tar; + finalFileName = NULL; + resolved = true; + ENGINE_printLog(engine, "Loading bundle %s\n", entryPath); } else { // Not a valid tar file. - free(engine.tar); - engine.tar = NULL; + free(tar); } } - if (engine.tar != NULL) { - // It is a tar file, we need to look for a "main.wren" entry point. - strcpy(pathBuf, mainFileName); - } else { - // Not a tar file, use the given path or main.wren - strcpy(pathBuf, base); - strcat(pathBuf, !autoResolve ? fileName : mainFileName); - engine.argv[1] = strdup(pathBuf); - strcpy(pathBuf, !autoResolve ? fileName : mainFileName); - } - - if (fileName != NULL) { - free(fileName); - } - - // The basepath is incorporated later, so we pass the basename version to this method. - gameFile = ENGINE_readFile(&engine, pathBuf, &gameFileLength); - if (gameFile == NULL) { - if (engine.tar != NULL) { - ENGINE_printLog(&engine, "Error: Could not load %s in bundle.\n", pathBuf); + if (engine->tar == NULL) { + if (autoResolve) { + strcpy(entryPath, base); + strcat(entryPath, mainFileName); + finalFileName = NULL; + resolved = doesFileExist(entryPath); } else { - ENGINE_printLog(&engine, "Error: Could not load %s.\n", pathBuf); + resolved = true; } - printUsage(&engine); - result = EXIT_FAILURE; - goto cleanup; } } - result = ENGINE_start(&engine); - if (result == EXIT_FAILURE) { - goto cleanup; - } - - // Configure Wren VM - vm = VM_create(&engine); - WrenInterpretResult interpreterResult; - loop.vm = vm; - - // Load user game file - SDL_Thread* recordThread = NULL; - - WrenHandle* initMethod = NULL; - - interpreterResult = wrenInterpret(vm, "main", gameFile); - free(gameFile); - if (interpreterResult != WREN_RESULT_SUCCESS) { - result = EXIT_FAILURE; - goto vm_cleanup; + if (!engine->fused && !resolved) { + ENGINE_printLog(engine, "Error: Could not find an entry point at: %s\n", dirname(entryPath)); + printUsage(engine); + return NULL; + } else { + free(entryArgument); + engine->argv[1] = strdup(entryPath); + strcpy(entryPath, finalFileName == NULL ? mainFileName : finalFileName); } - // Load the class into slot 0. - + return entryPath; +} - wrenEnsureSlots(vm, 3); - initMethod = wrenMakeCallHandle(vm, "init()"); - wrenGetVariable(vm, "main", "Game", 0); - loop.gameClass = wrenGetSlotHandle(vm, 0); - loop.updateMethod = wrenMakeCallHandle(vm, "update()"); - loop.drawMethod = wrenMakeCallHandle(vm, "draw(_)"); - SDL_SetRenderDrawColor(engine.renderer, 0x00, 0x00, 0x00, 0xFF); +int main(int argc, char* argv[]) +{ + // configuring the buffer has to be first - // Initiate game loop + setbuf(stdout, NULL); + setvbuf(stdout, NULL, _IONBF, 0); + setbuf(stderr, NULL); + setvbuf(stderr, NULL, _IONBF, 0); - wrenSetSlotHandle(vm, 0, loop.gameClass); - interpreterResult = wrenCall(vm, initMethod); - if (interpreterResult != WREN_RESULT_SUCCESS) { - result = EXIT_FAILURE; - goto vm_cleanup; - } - // Release this handle if it finished successfully - wrenReleaseHandle(vm, initMethod); - initMethod = NULL; - engine.initialized = true; + int result = EXIT_SUCCESS; + INIT_TO_ZERO(ENGINE, engine); - SDL_SetWindowPosition(engine.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - SDL_ShowWindow(engine.window); + ENGINE_init(&engine); + engine.argv = calloc(max(2, argc), sizeof(char*)); + engine.argv[0] = argv[0]; + engine.argv[1] = NULL; + bool autoResolve = true; - // Resizing from init must happen before we begin recording - if (engine.record.makeGif) { - recordThread = SDL_CreateThread(ENGINE_record, "DOMErecorder", &engine); - } - loop.lag = loop.MS_PER_FRAME; - result = LOOP_processInput(&loop); +#ifndef __EMSCRIPTEN__ + result = FUSE_introspectBinary(&engine); if (result != EXIT_SUCCESS) { - goto vm_cleanup; + goto cleanup; } - loop.windowBlurred = false; - loop.previousTime = SDL_GetPerformanceCounter(); - #ifdef __EMSCRIPTEN__ - emscripten_set_main_loop_arg(DOME_loop, &loop, 0, true); - #endif - while (engine.running) { - - // processInput() - if (loop.windowBlurred) { - result = LOOP_processInput(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - } - loop.currentTime = SDL_GetPerformanceCounter(); - loop.elapsed = 1000 * (loop.currentTime - loop.previousTime) / (double) SDL_GetPerformanceFrequency(); - loop.previousTime = loop.currentTime; - - - // If we aren't focused, we skip the update loop and let the CPU sleep - // to be good citizens - if (loop.windowBlurred) { - SDL_Delay(50); - continue; - } - - if(fabs(loop.elapsed - 1.0/120.0) < .0002){ - loop.elapsed = 1.0/120.0; - } - if(fabs(loop.elapsed - 1.0/60.0) < .0002){ - loop.elapsed = 1.0/60.0; - } - if(fabs(loop.elapsed - 1.0/30.0) < .0002){ - loop.elapsed = 1.0/30.0; - } - loop.lag += loop.elapsed; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"version", 'v', OPTPARSE_NONE}, +#ifdef __MINGW32__ + {"console", 'c', OPTPARSE_NONE}, +#endif + {"buffer", 'b', OPTPARSE_REQUIRED}, + {"debug", 'd', OPTPARSE_NONE}, + {0} + }; - if (engine.lockstep) { - if (loop.lag >= loop.MS_PER_FRAME) { - result = LOOP_processInput(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - result = LOOP_update(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - result = LOOP_render(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - loop.lag = mid(0, loop.lag - loop.MS_PER_FRAME, loop.MS_PER_FRAME); - LOOP_flip(&loop); - } - } else { - loop.attempts = 5; - while (loop.attempts > 0 && loop.lag >= loop.MS_PER_FRAME) { - loop.attempts--; - - result = LOOP_processInput(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - // update() - result = LOOP_update(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - loop.lag -= loop.MS_PER_FRAME; - } - // render(); - result = LOOP_render(&loop); - if (result != EXIT_SUCCESS) { - goto vm_cleanup; - } - if (loop.attempts == 0) { - loop.lag = 0; + int option; + char **subargv = NULL; + struct optparse options; + optparse_init(&options, argv); + options.permute = 0; + + if (!engine.fused) { + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'b': + { + int shift = atoi(options.optarg); + if (shift == 0) { + // If it wasn't valid, set to a meaningful default. + AUDIO_BUFFER_SIZE = 2048; + } + AUDIO_BUFFER_SIZE = 1 << shift; + } break; +#ifdef __MINGW32__ + case 'c': + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } break; +#endif + case 'd': + { + DEBUG_MODE = true; + ENGINE_printLog(&engine, "Debug Mode enabled\n"); + } break; + case 'h': + { + printTitle(&engine); + printUsage(&engine); + goto cleanup; + } + case 'v': + { + printTitle(&engine); + printVersion(&engine); + goto cleanup; + } + case '?': + { + fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); + result = EXIT_FAILURE; + goto cleanup; + } } - LOOP_flip(&loop); - } - - - if (!engine.vsyncEnabled) { - SDL_Delay(1); } - } -vm_cleanup: - if (recordThread != NULL) { - SDL_WaitThread(recordThread, NULL); - } - // Finish processing async threads so we can release resources - ENGINE_finishAsync(&engine); - SDL_Event event; - while(SDL_PollEvent(&event)) { - if (event.type == SDL_USEREVENT) { - if (event.user.code == EVENT_LOAD_FILE) { - FILESYSTEM_loadEventComplete(&event); + if (argc > 1) { + static const struct { + char name[8]; + char letter; + int (*cmd)(ENGINE*, char **); + } cmds[] = { + {"fuse", 'f', FUSE_perform }, + {"embed", 'e', EMBED_perform }, + {"help", 'h', HELP_perform }, + {"nest", 'n', NEST_perform } + }; + int ncmds = sizeof(cmds) / sizeof(*cmds); + subargv = argv + options.optind; + // If we match a subcommand, execute it + for (int i = 0; i < ncmds; i++) { + if (!strncmp(cmds[i].name, subargv[0], strlen(subargv[0]))) { + result = cmds[i].cmd(&engine, subargv); + goto cleanup; + } } } } - // Free resources - ENGINE_reportError(&engine); - - if (initMethod != NULL) { - wrenReleaseHandle(vm, initMethod); + // we are trying to play the game + // Get non-option args list + int domeArgCount = 1; + char* otherArg = NULL; + while ((otherArg = optparse_arg(&options))) { + engine.argv[domeArgCount] = strdup(otherArg); + domeArgCount++; } + // This has to be here because we modify the value of domeArgCount after + // to account for a strict 2-arg requirement. (See dome.Process documentation) + autoResolve = (domeArgCount == 1); + domeArgCount = max(2, domeArgCount); + engine.argv = realloc(engine.argv, sizeof(char*) * domeArgCount); + engine.argc = domeArgCount; +#endif - LOOP_release(&loop); - - if (bufferClass != NULL) { - wrenReleaseHandle(vm, bufferClass); + char* entryPath = resolveEntryPath(&engine, engine.argv[1], autoResolve); + if (entryPath == NULL) { + goto cleanup; } - INPUT_release(vm); - - AUDIO_ENGINE_halt(engine.audioEngine); - AUDIO_ENGINE_releaseHandles(engine.audioEngine, vm); + result = DOME_begin(&engine, entryPath); + free(entryPath); cleanup: BASEPATH_free(); - VM_free(vm); - result = engine.exit_status; ENGINE_free(&engine); //Quit SDL subsystems if (strlen(SDL_GetError()) > 0) { @@ -808,3 +404,4 @@ int main(int argc, char* args[]) return result; } + diff --git a/src/math.c b/src/math.c index 497e1264..33758258 100644 --- a/src/math.c +++ b/src/math.c @@ -54,6 +54,13 @@ lerp(float a, float b, float f) { return (a * (1.0 - f)) + (b * f); } +inline internal void +swap(float* a, float* b) { + float temp = *a; + *a = *b; + *b = temp; +} + internal int64_t max(int64_t n1, int64_t n2) { if (n1 > n2) { diff --git a/src/modules/dome.c b/src/modules/dome.c index dcc5647d..cb11d993 100644 --- a/src/modules/dome.c +++ b/src/modules/dome.c @@ -21,6 +21,21 @@ PROCESS_getArguments(WrenVM* vm) { } } +internal void +PROCESS_getErrorDialog(WrenVM* vm) { + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + bool errorDialogEnabled = engine->debug.errorDialog; + wrenSetSlotBool(vm, 0, errorDialogEnabled); +} + +internal void +PROCESS_setErrorDialog(WrenVM* vm) { + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + ASSERT_SLOT_TYPE(vm, 1, BOOL, "value"); + bool errorDialogEnabled = wrenGetSlotBool(vm, 1); + engine->debug.errorDialog = errorDialogEnabled; +} + internal void STRING_UTILS_toLowercase(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, STRING, "string"); diff --git a/src/modules/dome.wren b/src/modules/dome.wren index 3dbc25d7..4a9bfb1d 100644 --- a/src/modules/dome.wren +++ b/src/modules/dome.wren @@ -46,6 +46,8 @@ class Version { class Process { foreign static args foreign static f_exit(n) + foreign static errorDialog=(value) + foreign static errorDialog static exit(n) { f_exit(n) Fiber.suspend() diff --git a/src/modules/font.c b/src/modules/font.c index ff5b968c..c594d87f 100644 --- a/src/modules/font.c +++ b/src/modules/font.c @@ -143,7 +143,6 @@ FONT_RASTER_getArea(WrenVM* vm) { const char* text = wrenGetSlotString(vm, 1); stbtt_fontinfo info = raster->font->info; - unsigned char *bitmap; int w, h; int fontHeight = raster->height; @@ -171,7 +170,7 @@ FONT_RASTER_getArea(WrenVM* vm) { int lsb; int oY, oX; stbtt_GetCodepointHMetrics(&info, codepoint, &ax, &lsb); - bitmap = stbtt_GetCodepointBitmap(&info, 0, scale, codepoint, &w, &h, &oX, &oY); + stbtt_GetCodepointBitmap(&info, 0, scale, codepoint, &w, &h, &oX, &oY); posX += oX; posX += ax * scale; diff --git a/src/modules/graphics.c b/src/modules/graphics.c index 7d3a6eaa..f7c33b9f 100644 --- a/src/modules/graphics.c +++ b/src/modules/graphics.c @@ -14,8 +14,7 @@ CANVAS_print(WrenVM* vm) { } internal void -CANVAS_pget(WrenVM* vm) -{ +CANVAS_pget(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ENGINE* engine = (ENGINE*)wrenGetUserData(vm); @@ -25,9 +24,9 @@ CANVAS_pget(WrenVM* vm) wrenEnsureSlots(vm, 1); wrenSetSlotDouble(vm, 0, c); } + internal void -CANVAS_pset(WrenVM* vm) -{ +CANVAS_pset(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ASSERT_SLOT_TYPE(vm, 3, NUM, "color"); @@ -39,8 +38,7 @@ CANVAS_pset(WrenVM* vm) } internal void -CANVAS_circle_filled(WrenVM* vm) -{ +CANVAS_circle_filled(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ASSERT_SLOT_TYPE(vm, 3, NUM, "radius"); @@ -58,8 +56,7 @@ CANVAS_circle_filled(WrenVM* vm) } internal void -CANVAS_circle(WrenVM* vm) -{ +CANVAS_circle(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ASSERT_SLOT_TYPE(vm, 3, NUM, "radius"); @@ -75,9 +72,9 @@ CANVAS_circle(WrenVM* vm) uint32_t c = round(wrenGetSlotDouble(vm, 4)); ENGINE_circle(engine, x, y, r, c); } + internal void -CANVAS_line(WrenVM* vm) -{ +CANVAS_line(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x1"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y1"); ASSERT_SLOT_TYPE(vm, 3, NUM, "x2"); @@ -95,8 +92,7 @@ CANVAS_line(WrenVM* vm) } internal void -CANVAS_ellipse(WrenVM* vm) -{ +CANVAS_ellipse(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x1"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y1"); ASSERT_SLOT_TYPE(vm, 3, NUM, "x2"); @@ -112,8 +108,7 @@ CANVAS_ellipse(WrenVM* vm) } internal void -CANVAS_ellipsefill(WrenVM* vm) -{ +CANVAS_ellipsefill(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x1"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y1"); ASSERT_SLOT_TYPE(vm, 3, NUM, "x2"); @@ -129,8 +124,49 @@ CANVAS_ellipsefill(WrenVM* vm) } internal void -CANVAS_rect(WrenVM* vm) -{ +CANVAS_triangle(WrenVM* vm) { + ASSERT_SLOT_TYPE(vm, 1, NUM, "x0"); + ASSERT_SLOT_TYPE(vm, 2, NUM, "y0"); + ASSERT_SLOT_TYPE(vm, 3, NUM, "x1"); + ASSERT_SLOT_TYPE(vm, 4, NUM, "y1"); + ASSERT_SLOT_TYPE(vm, 5, NUM, "x2"); + ASSERT_SLOT_TYPE(vm, 6, NUM, "y2"); + ASSERT_SLOT_TYPE(vm, 7, NUM, "color"); + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + int64_t x0 = round(wrenGetSlotDouble(vm, 1)); + int64_t y0 = round(wrenGetSlotDouble(vm, 2)); + int64_t x1 = round(wrenGetSlotDouble(vm, 3)); + int64_t y1 = round(wrenGetSlotDouble(vm, 4)); + int64_t x2 = round(wrenGetSlotDouble(vm, 5)); + int64_t y2 = round(wrenGetSlotDouble(vm, 6)); + + uint32_t c = round(wrenGetSlotDouble(vm, 7)); + ENGINE_triangle(engine, x0, y0, x1, y1, x2, y2, c); +} + +internal void +CANVAS_trianglefill(WrenVM* vm) { + ASSERT_SLOT_TYPE(vm, 1, NUM, "x0"); + ASSERT_SLOT_TYPE(vm, 2, NUM, "y0"); + ASSERT_SLOT_TYPE(vm, 3, NUM, "x1"); + ASSERT_SLOT_TYPE(vm, 4, NUM, "y1"); + ASSERT_SLOT_TYPE(vm, 5, NUM, "x2"); + ASSERT_SLOT_TYPE(vm, 6, NUM, "y2"); + ASSERT_SLOT_TYPE(vm, 7, NUM, "color"); + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + int64_t x0 = round(wrenGetSlotDouble(vm, 1)); + int64_t y0 = round(wrenGetSlotDouble(vm, 2)); + int64_t x1 = round(wrenGetSlotDouble(vm, 3)); + int64_t y1 = round(wrenGetSlotDouble(vm, 4)); + int64_t x2 = round(wrenGetSlotDouble(vm, 5)); + int64_t y2 = round(wrenGetSlotDouble(vm, 6)); + + uint32_t c = round(wrenGetSlotDouble(vm, 7)); + ENGINE_trianglefill(engine, x0, y0, x1, y1, x2, y2, c); +} + +internal void +CANVAS_rect(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ASSERT_SLOT_TYPE(vm, 3, NUM, "w"); @@ -154,8 +190,7 @@ CANVAS_rect(WrenVM* vm) } internal void -CANVAS_rectfill(WrenVM* vm) -{ +CANVAS_rectfill(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "x"); ASSERT_SLOT_TYPE(vm, 2, NUM, "y"); ASSERT_SLOT_TYPE(vm, 3, NUM, "w"); @@ -179,8 +214,7 @@ CANVAS_rectfill(WrenVM* vm) } internal void -CANVAS_cls(WrenVM* vm) -{ +CANVAS_cls(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "color"); uint32_t c = round(wrenGetSlotDouble(vm, 1)); ENGINE* engine = (ENGINE*)wrenGetUserData(vm); @@ -205,12 +239,27 @@ CANVAS_getWidth(WrenVM* vm) { ENGINE* engine = (ENGINE*)wrenGetUserData(vm); wrenSetSlotDouble(vm, 0, engine->canvas.width); } + internal void CANVAS_getHeight(WrenVM* vm) { ENGINE* engine = (ENGINE*)wrenGetUserData(vm); wrenSetSlotDouble(vm, 0, engine->canvas.height); } +internal void +CANVAS_getOffsetX(WrenVM* vm) { + wrenEnsureSlots(vm, 1); + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + wrenSetSlotDouble(vm, 0, engine->canvas.offsetX); +} + +internal void +CANVAS_getOffsetY(WrenVM* vm) { + wrenEnsureSlots(vm, 1); + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + wrenSetSlotDouble(vm, 0, engine->canvas.offsetY); +} + internal void CANVAS_resize(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, NUM, "width"); diff --git a/src/modules/graphics.wren b/src/modules/graphics.wren index 11308ea9..a6a332e4 100644 --- a/src/modules/graphics.wren +++ b/src/modules/graphics.wren @@ -13,6 +13,8 @@ class Canvas { Fiber.abort("Default font must be a font name") } } + + static font { __defaultFont } foreign static f_resize(width, height, color) static offset() { offset(0, 0) } @@ -51,6 +53,8 @@ class Canvas { foreign static f_circlefill(x, y, r, c) foreign static f_ellipse(x1, y1, x2, y2, c) foreign static f_ellipsefill(x1, y1, x2, y2, c) + foreign static f_triangle(x0, y0, x1, y1, x2, y2, c) + foreign static f_trianglefill(x0, y0, x1, y1, x2, y2, c) static pset(x, y, c) { if (c is Color) { @@ -111,6 +115,20 @@ class Canvas { f_circlefill(x, y, r, c) } } + static triangle(x0, y0, x1, y1, x2, y2, c) { + if (c is Color) { + f_triangle(x0, y0, x1, y1, x2, y2, c.toNum) + } else { + f_triangle(x0, y0, x1, y1, x2, y2, c) + } + } + static trianglefill(x0, y0, x1, y1, x2, y2, c) { + if (c is Color) { + f_trianglefill(x0, y0, x1, y1, x2, y2, c.toNum) + } else { + f_trianglefill(x0, y0, x1, y1, x2, y2, c) + } + } static print(str, x, y, c, font) { if (Font[font] != null) { Font[font].print(str, x, y, c) @@ -160,12 +178,19 @@ class Canvas { } f_cls(color.toNum) } + foreign static width foreign static height static draw(object, x, y) { object.draw(x, y) } + + foreign static offsetX + foreign static offsetY + + static offset { Vector.new (offsetX, offsetY) } + static offset=(v) { offset(v.x, v.y) } } diff --git a/src/modules/input.c b/src/modules/input.c index 90113068..70dc6fea 100644 --- a/src/modules/input.c +++ b/src/modules/input.c @@ -116,6 +116,7 @@ INPUT_clearText(WrenVM* vm) { if (!inputCaptured) { return WREN_RESULT_SUCCESS; } + wrenEnsureSlots(vm, 1); wrenSetSlotHandle(vm, 0, keyboardClass); return wrenCall(vm, keyboardClearTextMethod); } @@ -253,6 +254,25 @@ MOUSE_getHidden(WrenVM* vm) { wrenSetSlotBool(vm, 0, !shown); } +internal void +MOUSE_setCursor(WrenVM* vm) { + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + ASSERT_SLOT_TYPE(vm, 1, STRING, "cursorName"); + const char* name = wrenGetSlotString(vm, 1); + int32_t cursorID = ENGINE_findMouseCursorIndex(engine, name); + if (cursorID < 0) { + VM_ABORT(vm, "invalid cursor name"); + } + ENGINE_setMouseCursor(engine, cursorID); +} + +internal void +MOUSE_getCursor(WrenVM* vm) { + ENGINE* engine = (ENGINE*)wrenGetUserData(vm); + const char* name = ENGINE_getMouseCursor(engine); + wrenSetSlotString(vm, 0, name); +} + typedef struct { int instanceId; SDL_GameController* controller; @@ -475,7 +495,7 @@ CLIPBOARD_getContent(WrenVM* vm) { internal void CLIPBOARD_setContent(WrenVM* vm) { ASSERT_SLOT_TYPE(vm, 1, STRING, "text"); - char* text = wrenGetSlotString(vm, 1); + const char* text = wrenGetSlotString(vm, 1); int result = SDL_SetClipboardText(text); if (result < 0) { VM_ABORT(vm, SDL_GetError()); diff --git a/src/modules/input.wren b/src/modules/input.wren index 3192209a..303d50b1 100644 --- a/src/modules/input.wren +++ b/src/modules/input.wren @@ -115,6 +115,8 @@ class Mouse { foreign static hidden=(value) foreign static relative=(value) foreign static relative + foreign static cursor=(value) + foreign static cursor static isButtonPressed(key) { return Mouse[StringUtils.toLowercase(key)].down diff --git a/src/modules/io.c b/src/modules/io.c index f1c8a35f..a81a487b 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -138,7 +138,7 @@ FILESYSTEM_loadEventHandler(void* data) { // Thread: Async ENGINE* engine = (ENGINE*)wrenGetUserData(task->vm); - task->buffer = ENGINE_readFile(engine, task->name, &task->length); + task->buffer = ENGINE_readFile(engine, task->name, &task->length, NULL); SDL_Event event; SDL_memset(&event, 0, sizeof(event)); @@ -175,7 +175,7 @@ FILESYSTEM_loadSync(WrenVM* vm) { ENGINE* engine = (ENGINE*)wrenGetUserData(vm); size_t length; - char* data = ENGINE_readFile(engine, path, &length); + char* data = ENGINE_readFile(engine, path, &length, NULL); if (data == NULL) { size_t len = 22 + strlen(path); char message[len]; diff --git a/src/modules/json.c b/src/modules/json.c index a74ca9ab..8ab598b9 100644 --- a/src/modules/json.c +++ b/src/modules/json.c @@ -38,7 +38,7 @@ JSON_STREAM_value(WrenVM * vm) { internal void JSON_STREAM_error_message(WrenVM * vm) { const char * error = json_get_error(jsonStream); - if(error) { + if (error) { wrenSetSlotString(vm, 0, error); return; } diff --git a/src/modules/json.wren b/src/modules/json.wren index ab6835f7..27199a7a 100644 --- a/src/modules/json.wren +++ b/src/modules/json.wren @@ -141,7 +141,12 @@ class JsonEncoder { _circularStack = JsonOptions.contains(options, JsonOptions.checkCircular) ? [] : null } - isCircle(value) { _circularStack == null || !_circularStack.any { |v| Object.same(value, v) } } + isCircle(value) { + if (_circularStack == null) { + return false + } + return _circularStack.any { |v| Object.same(value, v) } + } push(value) { if (_circularStack != null) { @@ -178,14 +183,22 @@ class JsonEncoder { if (value is List) { push(value) - var substrings = value.map { |item| encode(item) } + var substrings = [] + for (item in value) { + substrings.add(encode(item)) + } pop() return "[" + substrings.join(",") + "]" } if (value is Map) { push(value) - var substrings = value.keys.map { |key| "%(encode(key)):%(encode(value[key]))" } + var substrings = [] + for (key in value.keys) { + var keyValue = this.encode(value[key]) + var encodedKey = this.encode(key) + substrings.add("%(encodedKey):%(keyValue)") + } pop() return "{" + substrings.join(",") + "}" } diff --git a/src/modules/map.c b/src/modules/map.c index a09000eb..eda5cac7 100644 --- a/src/modules/map.c +++ b/src/modules/map.c @@ -161,7 +161,7 @@ MAP_freeFunctions(FUNCTION_NODE* node) { FUNCTION_NODE* nextNode; while (node != NULL) { nextNode = node->next; - free(node->signature); + free((char*) node->signature); free(node); node = nextNode; } @@ -171,7 +171,7 @@ MAP_freeClasses(CLASS_NODE* node) { CLASS_NODE* nextNode; while (node != NULL) { nextNode = node->next; - free(node->name); + free((char*) node->name); free(node); node = nextNode; } @@ -185,8 +185,8 @@ MAP_free(MAP* map) { MAP_freeFunctions(module->functions); MAP_freeClasses(module->classes); nextModule = module->next; - free(module->name); - free(module->source); + free((char*) module->name); + free((char*) module->source); free(module); module = nextModule; } diff --git a/src/modules/math.wren b/src/modules/math.wren index e31ccf4b..30d16d70 100644 --- a/src/modules/math.wren +++ b/src/modules/math.wren @@ -60,21 +60,13 @@ class Math { static max(a, b) { assertNum(a) assertNum(b) - if (a > b) { - return a - } else { - return b - } + return a.max(b) } static min(a, b) { assertNum(a) assertNum(b) - if (a < b) { - return a - } else { - return b - } + return a.min(b) } static sign(a) { @@ -97,11 +89,14 @@ class Math { a = b b = swap } - if (b < c) { - return b - } else { - return c - } + return b.min(c) + } + + static clamp(number, min, max) { + assertNum(number) + assertNum(min) + assertNum(max) + return number.clamp(min, max) } static lerp(low, value, high) { diff --git a/src/modules/random.c b/src/modules/random.c index c6e16b9e..51d9ed62 100644 --- a/src/modules/random.c +++ b/src/modules/random.c @@ -40,3 +40,51 @@ void RANDOM_float(WrenVM* vm) { double result = squirrel3Hash(rng->state++, rng->seed); wrenSetSlotDouble(vm, 0, result / CAP); } + +const uint32_t SQ5_BIT_NOISE1 = 0xd2a80a3f; +const uint32_t SQ5_BIT_NOISE2 = 0xa884f197; +const uint32_t SQ5_BIT_NOISE3 = 0x6C736F4B; +const uint32_t SQ5_BIT_NOISE4 = 0xB79F3ABB; +const uint32_t SQ5_BIT_NOISE5 = 0x1b56c4f5; + +uint32_t squirrel5Hash( uint32_t position, uint32_t seed ) { + uint32_t mangledBits = position; + mangledBits *= SQ5_BIT_NOISE1; + mangledBits += seed; + mangledBits ^= (mangledBits >> 9); + mangledBits += SQ5_BIT_NOISE2; + mangledBits ^= (mangledBits >> 11); + mangledBits *= SQ5_BIT_NOISE3; + mangledBits ^= (mangledBits >> 13); + mangledBits += SQ5_BIT_NOISE4; + mangledBits ^= (mangledBits >> 15); + mangledBits *= SQ5_BIT_NOISE5; + mangledBits ^= (mangledBits >> 17); + return mangledBits; +} + +void SQUIRREL5_noise(WrenVM* vm) { + uint32_t position = wrenGetSlotDouble(vm, 1); + uint32_t seed = wrenGetSlotDouble(vm, 2); + wrenSetSlotDouble(vm, 0, squirrel5Hash(position, seed)); +} + +typedef struct { + uint32_t seed; + uint32_t state; +} Squirrel5; + +void SQUIRREL5_allocate(WrenVM* vm) { + Squirrel5* rng = wrenSetSlotNewForeign(vm, 0, 0, sizeof(Squirrel5)); + uint32_t seed = wrenGetSlotDouble(vm, 1); + rng->seed = seed; + rng->state = 0; +} + +void SQUIRREL5_finalize(void* data) {} + +void SQUIRREL5_float(WrenVM* vm) { + Squirrel5* rng = wrenGetSlotForeign(vm, 0); + double result = squirrel5Hash(rng->state++, rng->seed); + wrenSetSlotDouble(vm, 0, result / CAP); +} diff --git a/src/modules/random.wren b/src/modules/random.wren index 2dc267d1..eb34b323 100644 --- a/src/modules/random.wren +++ b/src/modules/random.wren @@ -38,4 +38,42 @@ foreign class Squirrel3 { } } +foreign class Squirrel5 { + static noise(x) { noise(x, 0) } + foreign static noise(x, seed) + + static new() { Squirrel5.new(Platform.time) } + construct new(seed) {} + + foreign float() + float(end) { float() * end } + float(start, end) { start + float(end - start) } + int(end) { float(end).floor } + int(start, end) { float(start, end).floor } + + sample(list) { list[int(list.count)] } + + sample(list, count) { + if (count > list.count) { + Fiber.abort("Cannot sample more items than the list contains") + } + var newList = shuffle(list.toList) + var end = list.count - 1 + for (i in end..count) { + newList.removeAt(i) + } + return newList + } + + shuffle(list) { + var n = list.count + var j + for (i in 0...n) { + j = int(i + 1) + list.swap(j, i) + } + return list + } +} + var Random = Squirrel3 diff --git a/src/plugin.c b/src/plugin.c index dbe147b2..c6b553e7 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -30,6 +30,7 @@ PLUGIN_COLLECTION_init(ENGINE* engine) { PLUGIN_COLLECTION plugins = engine->plugins; plugins.max = 0; plugins.count = 0; + plugins.errorReason[0] = '\0'; assert(plugins.count <= plugins.max); plugins.active = NULL; plugins.name = NULL; @@ -229,63 +230,19 @@ PLUGIN_COLLECTION_add(ENGINE* engine, const char* name) { return DOME_RESULT_SUCCESS; } - -internal DOME_Result -DOME_registerModuleImpl(DOME_Context ctx, const char* name, const char* source) { - - ENGINE* engine = (ENGINE*)ctx; - MAP* moduleMap = &(engine->moduleMap); - if (MAP_addModule(moduleMap, name, source)) { - return DOME_RESULT_SUCCESS; - } - return DOME_RESULT_FAILURE; -} - -internal DOME_Result -DOME_registerFnImpl(DOME_Context ctx, const char* moduleName, const char* signature, DOME_ForeignFn method) { - - ENGINE* engine = (ENGINE*)ctx; - MAP* moduleMap = &(engine->moduleMap); - if (MAP_addFunction(moduleMap, moduleName, signature, (WrenForeignMethodFn)method)) { - return DOME_RESULT_SUCCESS; - } - - return DOME_RESULT_FAILURE; -} - -internal DOME_Result -DOME_registerClassImpl(DOME_Context ctx, const char* moduleName, const char* className, DOME_ForeignFn allocate, DOME_FinalizerFn finalize) { - - // TODO: handle null allocate ptr - ENGINE* engine = (ENGINE*)ctx; - MAP* moduleMap = &(engine->moduleMap); - if (MAP_addClass(moduleMap, moduleName, className, (WrenForeignMethodFn)allocate, (WrenFinalizerFn)finalize)) { - return DOME_RESULT_SUCCESS; - } - - return DOME_RESULT_FAILURE; -} - internal void -DOME_lockModuleImpl(DOME_Context ctx, const char* moduleName) { - ENGINE* engine = (ENGINE*)ctx; - MAP* moduleMap = &(engine->moduleMap); - - MAP_lockModule(moduleMap, moduleName); -} - -internal DOME_Context -DOME_getVMContext(WrenVM* vm) { - return wrenGetUserData(vm); +PLUGIN_COLLECTION_setErrorReason(ENGINE* engine, const char* error, size_t length) { + char* reason = (char*)&(engine->plugins.errorReason); + reason[0] = '\0'; + size_t size = min(length, ERROR_MAX_LENGTH - 1); + strncat(reason, error, size); } -internal void -DOME_printLog(DOME_Context ctx, const char* text, ...) { - va_list args; - va_start(args, text); - ENGINE_printLogVariadic(ctx, text, args); - va_end(args); +internal const char* +PLUGIN_COLLECTION_getErrorReason(ENGINE* engine) { + return (const char*)&(engine->plugins.errorReason); } + WREN_API_v0 wren_v0 = { .getUserData = wrenGetUserData, .ensureSlots = wrenEnsureSlots, @@ -331,15 +288,6 @@ WREN_API_v0 wren_v0 = { .interpret = wrenInterpret, }; -DOME_API_v0 dome_v0 = { - .registerModule = DOME_registerModuleImpl, - .registerFn = DOME_registerFnImpl, - .registerClass = DOME_registerClassImpl, - .lockModule = DOME_lockModuleImpl, - .getContext = DOME_getVMContext, - .log = DOME_printLog -}; - external void* DOME_getAPI(API_TYPE api, int version) { if (api == API_DOME) { @@ -354,6 +302,18 @@ DOME_getAPI(API_TYPE api, int version) { if (version == 0) { return &audio_v0; } + } else if (api == API_CANVAS) { + if (version == 0) { + return &canvas_v0; + } + } else if (api == API_BITMAP) { + if (version == 0) { + return &bitmap_v0; + } + } else if (api == API_IO) { + if (version == 0) { + return &io_v0; + } } return NULL; diff --git a/src/plugin.h b/src/plugin.h index 6bb21ce5..ce5d4057 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -6,11 +6,15 @@ struct ENGINE_t; typedef DOME_Result (*DOME_Plugin_Init_Hook) (DOME_getAPIFunction apiFn, DOME_Context context); typedef DOME_Result (*DOME_foreignFn) (DOME_Context context, WrenVM* vm); +#define ERROR_MAX_LENGTH 4096 + typedef struct { // Total allocated size size_t max; // Number in use size_t count; + + // Struct of arrays for performance bool* active; const char** name; void** objectHandle; @@ -18,6 +22,9 @@ typedef struct { DOME_Plugin_Hook* postUpdateHook; DOME_Plugin_Hook* preDrawHook; DOME_Plugin_Hook* postDrawHook; + + // Singleton pointer to an error reason + char errorReason[ERROR_MAX_LENGTH]; } PLUGIN_COLLECTION; typedef enum { @@ -35,5 +42,7 @@ internal void PLUGIN_COLLECTION_free(struct ENGINE_t* engine); internal DOME_Result PLUGIN_COLLECTION_runHook(struct ENGINE_t* engine, DOME_PLUGIN_HOOK hook); internal DOME_Result PLUGIN_COLLECTION_add(struct ENGINE_t* engine, const char* name); +internal const char* PLUGIN_COLLECTION_getErrorReason(struct ENGINE_t* engine); +internal void PLUGIN_COLLECTION_setErrorReason(struct ENGINE_t* engine, const char* reason, size_t length); #endif diff --git a/src/tools/embed-standalone.c b/src/tools/embed-standalone.c new file mode 100644 index 00000000..02d3d4a3 --- /dev/null +++ b/src/tools/embed-standalone.c @@ -0,0 +1,83 @@ +#ifdef __MINGW32__ +#include +#endif +#include +#include "embedlib.c" + +internal char* +EMBED_readEntireFile(char* path, size_t* lengthPtr) { + FILE* file = fopen(path, "r"); + if (file == NULL) { + return NULL; + } + + char* source = NULL; + if (fseek(file, 0L, SEEK_END) == 0) { + + // Get the size of the file. + long bufsize = ftell(file); + + // Allocate our buffer to that size. + source = malloc(sizeof(char) * (bufsize + 1)); + + // Go back to the start of the file. + if (fseek(file, 0L, SEEK_SET) != 0) { + // Error + } + + // Read the entire file into memory. + size_t newLen = fread(source, sizeof(char), bufsize, file); + + if (ferror(file) != 0) { + fclose(file); + return NULL; + } + + if (lengthPtr != NULL) { + *lengthPtr = newLen; + } + + // Add NULL, Just to be safe. + source[newLen++] = '\0'; + } + + fclose(file); + return source; +} + +int main(int argc, char* args[]) +{ + if (argc < 2) { + printf("./embed sourceFile [moduleName] [destinationFile]\n"); + printf("Not enough arguments.\n"); + return EXIT_FAILURE; + } + + size_t length; + char* fileName = args[1]; + char* fileToConvert = EMBED_readEntireFile(fileName, &length); + + if (fileToConvert == NULL) { + fputs("Error reading file\n", stderr); + return EXIT_FAILURE; + } + + // TODO: Maybe use the filename as a default identifier + char* moduleName = "wren_module_test"; + + if(argc > 2) { + // TODO: Maybe sanitize moduleName to be valid C identifier? + moduleName = args[2]; + } + + char* destination = NULL; + if(argc > 3) { + destination = args[3]; + } else { + // Example: main.wren.inc + destination = strcat(strdup(fileName), ".inc"); + } + + return EMBED_encode(fileToConvert, length, moduleName, destination); +} + diff --git a/src/tools/embed.c b/src/tools/embed.c new file mode 100644 index 00000000..4ae12189 --- /dev/null +++ b/src/tools/embed.c @@ -0,0 +1,68 @@ +#include "tools.h" +#include "embedlib.c" + +internal void +EMBED_usage(ENGINE* engine) { + ENGINE_printLog(engine, "\nUsage: \n"); + ENGINE_printLog(engine, " dome embed [] [] \n"); + ENGINE_printLog(engine, " dome embed [options] \n"); + ENGINE_printLog(engine, "\nOptions: \n"); + ENGINE_printLog(engine, " -h --help Show this help message.\n"); + ENGINE_printLog(engine, "\n"); +} + +internal int +EMBED_perform(ENGINE* engine, char** argv) { + struct optparse options; + int option; + optparse_init(&options, argv); + options.permute = 0; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {0} + }; + + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + printTitle(engine); + EMBED_usage(engine); + return EXIT_SUCCESS; + case '?': + ENGINE_printLog(engine, "dome: %s: %s\n", argv[0], options.errmsg); + EMBED_usage(engine); + return EXIT_FAILURE; + } + } + argv += options.optind; + + char* fileName = optparse_arg(&options); + if (fileName == NULL) { + ENGINE_printLog(engine, "dome: Missing file name.\n"); + EMBED_usage(engine); + return EXIT_FAILURE; + } + size_t length; + char* fileToConvert = ENGINE_readFile(engine, fileName, &length, NULL); + + if (fileToConvert == NULL) { + ENGINE_printLog(engine, "dome: Error reading file: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + char* moduleName = optparse_arg(&options); + if (moduleName == NULL) { + moduleName = "wren_module_test"; + } + char* destination = optparse_arg(&options); + bool shouldFree = false; + if (destination == NULL) { + destination = strdup(fileName); + destination = realloc(destination, sizeof(char) * (strlen(destination) + 4) + 1); + strcat(destination, ".inc"); + shouldFree = true; + } + + int result = EMBED_encode(fileToConvert, length, moduleName, destination); + free(fileToConvert); + return result; +} diff --git a/src/tools/embedlib.c b/src/tools/embedlib.c new file mode 100644 index 00000000..1fc8a2cb --- /dev/null +++ b/src/tools/embedlib.c @@ -0,0 +1,53 @@ +// Converts a Wren source file to a C include file +// Using standard IO only +#include +#include +#include +#include + +#ifndef internal +#define internal static +#endif + + +internal int +EMBED_encode(char* fileToConvert, size_t length, char* moduleName, char* destinationPath) { + FILE* fp = fopen(destinationPath, "w+"); + + fputs("// auto-generated file, do not modify\n", fp); + fputs("const char ", fp); + fputs(moduleName, fp); + fputs("[", fp); + fprintf(fp, "%zu", length + 1); + fputs("] = {", fp); + + // Encode chars + for (size_t i = 0; i < length; i++ ) { + char* ptr = fileToConvert + i; + if (*ptr == '\n') { + fputs("'\\n',", fp); + fputs("\n", fp); + } else { + fputs("'", fp); + + // TODO: Properly test the encoding with different source files + if (*ptr == '\'') { + fputs("\\\'", fp); + } else if (*ptr == '\\') { + fputs("\\\\", fp); + } else { + fwrite(ptr, sizeof(char), 1, fp); + } + + fputs("', ", fp); + } + } + + fputs("\0", fp); + fputs(" };\n", fp); + + fclose(fp); + + return EXIT_SUCCESS; +} + diff --git a/src/tools/fuse.c b/src/tools/fuse.c new file mode 100644 index 00000000..2446cfce --- /dev/null +++ b/src/tools/fuse.c @@ -0,0 +1,218 @@ +// This header is placed at the end of the file +#pragma pack(push, 1) +typedef struct { + uint8_t magic1[4]; + uint8_t version; + uint64_t offset; + uint8_t magic2[4]; +} DOME_FUSED_HEADER; +#pragma pack(pop) + +typedef struct { + FILE* fd; + size_t offset; +} FUSE_STREAM; + +internal void +FUSE_usage(ENGINE* engine) { + ENGINE_printLog(engine, "\nUsage: \n"); + ENGINE_printLog(engine, " dome fuse [] \n"); + ENGINE_printLog(engine, " dome fuse [options] \n"); + ENGINE_printLog(engine, "\nOptions: \n"); + ENGINE_printLog(engine, " -h --help Show this help message.\n"); + ENGINE_printLog(engine, "\n"); +} + +internal char* +FUSE_getExecutablePath() { + char* path = NULL; + size_t size = wai_getExecutablePath(NULL, 0, NULL); + if (size > 0) { + path = malloc(size + 1); + if (path != NULL) { + wai_getExecutablePath(path, size, NULL); + path[size] = '\0'; + } + } + return path; +} + +internal int +FUSE_perform(ENGINE* engine, char **argv) { + struct optparse options; + int option; + optparse_init(&options, argv); + options.permute = 0; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {0} + }; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + printTitle(engine); + FUSE_usage(engine); + return EXIT_SUCCESS; + case '?': + ENGINE_printLog(engine, "dome: %s: %s\n", argv[0], options.errmsg); + FUSE_usage(engine); + return EXIT_FAILURE; + } + } + argv += options.optind; + + char* fileName = optparse_arg(&options); + if (fileName == NULL) { + ENGINE_printLog(engine, "dome: Missing file name.\n"); + FUSE_usage(engine); + return EXIT_FAILURE; + } + char* outputFileName = optparse_arg(&options); + if (outputFileName == NULL) { +#if defined _WIN32 || __MINGW32__ + outputFileName = "game.exe"; +#else + outputFileName = "game"; +#endif + } + + char* binaryPath = FUSE_getExecutablePath(); + if (binaryPath != NULL) { + FILE* binaryIn = fopen(binaryPath, "rb"); + free(binaryPath); + binaryPath = NULL; + FILE* binaryOut = fopen(outputFileName, "wb"); + if (binaryIn == NULL || binaryOut == NULL) { + ENGINE_printLog(engine, "dome: Error loading DOME binary: %s \n", strerror(errno)); + return EXIT_FAILURE; + } + + mtar_t tar; + int tarResult = mtar_open(&tar, fileName, "rb"); + FILE* egg = tar.stream; + if (tarResult != MTAR_ESUCCESS) { + ENGINE_printLog(engine, "dome: Could not fuse: %s is not a valid EGG file.\n", fileName); + return EXIT_FAILURE; + } + + int c; + while((c = fgetc(binaryIn)) != EOF) { + fputc(c, binaryOut); + } + fclose(binaryIn); + uint64_t size = sizeof(DOME_FUSED_HEADER); + while((c = fgetc(egg)) != EOF) { + fputc(c, binaryOut); + size++; + } + mtar_close(&tar); + + DOME_FUSED_HEADER header; + memcpy(header.magic1, "DOME", 4); + memcpy(header.magic2, "DOME", 4); + header.version = 1; + header.offset = size; + fwrite(&header, sizeof(DOME_FUSED_HEADER), 1, binaryOut); + fclose(binaryOut); + + // Set permissions + // All owner permissions, everyone else reads and executes + int result = chmod(outputFileName, 0755); + if (result != 0) { + ENGINE_printLog(engine, "dome: Couldn't set permissions on the fused file: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + printf("Fused '%s' into DOME successfully as '%s'", fileName, outputFileName); + } + free(binaryPath); + return EXIT_SUCCESS; +} + +internal int +FUSE_read(mtar_t* tar, void *data, unsigned size) { + FUSE_STREAM* stream = tar->stream; + unsigned res = fread(data, 1, size, stream->fd); + return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL; +} + +internal int +FUSE_seek(mtar_t* tar, unsigned pos) { + FUSE_STREAM* stream = tar->stream; + assert(pos <= stream->offset); + int res = fseek(stream->fd, pos - stream->offset, SEEK_END); + return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL; +} + +internal int +FUSE_close(mtar_t* tar) { + FUSE_STREAM* stream = tar->stream; + if (stream->fd != NULL) { + fclose(stream->fd); + } + free(stream); + tar->stream = NULL; + return MTAR_ESUCCESS; +} + +internal int +FUSE_open(mtar_t* tar, FILE* fd, size_t offset) { + FUSE_STREAM* stream = malloc(sizeof(FUSE_STREAM)); + if (stream == NULL) { + return MTAR_EFAILURE; + } + stream->fd = fd; + stream->offset = offset; + + tar->stream = stream; + tar->read = FUSE_read; + tar->seek = FUSE_seek; + tar->close = FUSE_close; + tar->write = NULL; + + return MTAR_ESUCCESS; +} + +int FUSE_introspectBinary(ENGINE* engine) { + char* binaryPath = FUSE_getExecutablePath(); + if (binaryPath == NULL) { + ENGINE_printLog(engine, "dome: Could not allocate memory. Aborting.\n"); + return EXIT_FAILURE; + } + // Check if end of file has marker + FILE* self = fopen(binaryPath, "rb"); + if (self == NULL) { + ENGINE_printLog(engine, "dome: Could not read binary: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + int fileResult = fseek (self, -((long int)sizeof(DOME_FUSED_HEADER)), SEEK_END); + if (fileResult != 0) { + ENGINE_printLog(engine, "dome: Could not introspect binary: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + DOME_FUSED_HEADER header; + fileResult = fread(&header, sizeof(DOME_FUSED_HEADER), 1, self); + if (fileResult != 1) { + ENGINE_printLog(engine, "dome: Could not introspect binary: %s\n", strerror(errno)); + fclose(self); + return EXIT_FAILURE; + } + + if (memcmp("DOME", header.magic1, 4) == 0 && memcmp("DOME", header.magic2, 4) == 0) { + if (header.version == 1) { + engine->tar = malloc(sizeof(mtar_t)); + FUSE_open(engine->tar, self, header.offset); + engine->fused = true; + } else { + ENGINE_printLog(engine, "dome: Fused mode data is in the wrong format."); + fclose(self); + return EXIT_FAILURE; + } + } else { + // We aren't in fused mode. + fclose(self); + } + free(binaryPath); + return EXIT_SUCCESS; +} + diff --git a/src/tools/help.c b/src/tools/help.c new file mode 100644 index 00000000..6bfba027 --- /dev/null +++ b/src/tools/help.c @@ -0,0 +1,60 @@ +#include "tools.h" + +internal void +HELP_usage(ENGINE* engine) { + ENGINE_printLog(engine, "\nUsage: \n"); + ENGINE_printLog(engine, " dome help (-h | --help) | ()\n"); + ENGINE_printLog(engine, "\nOptions: \n"); + ENGINE_printLog(engine, " -h --help Show this help message.\n"); + ENGINE_printLog(engine, "\n"); +} + +internal int +HELP_perform(ENGINE* engine, char **argv) { + struct optparse options; + int option; + optparse_init(&options, argv); + options.permute = 0; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {0} + }; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + printTitle(engine); + HELP_usage(engine); + return EXIT_SUCCESS; + case '?': + ENGINE_printLog(engine, "dome: %s: %s\n", argv[0], options.errmsg); + printUsage(engine); + return EXIT_FAILURE; + } + } + argv += options.optind; + + char* command = optparse_arg(&options); + if (command == NULL) { + ENGINE_printLog(engine, "dome: command is missing.\n"); + HELP_usage(engine); + return EXIT_FAILURE; + } + if (STRINGS_EQUAL(command, "help")) { + printTitle(engine); + HELP_usage(engine); + } else if (STRINGS_EQUAL(command, "fuse")) { + printTitle(engine); + FUSE_usage(engine); + } else if (STRINGS_EQUAL(command, "embed")) { + printTitle(engine); + EMBED_usage(engine); + } else if (STRINGS_EQUAL(command, "nest")) { + printTitle(engine); + NEST_usage(engine); + } else { + ENGINE_printLog(engine, "dome: command %s was invalid.\n", command); + printUsage(engine); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/tools/nest.c b/src/tools/nest.c new file mode 100644 index 00000000..1ba58483 --- /dev/null +++ b/src/tools/nest.c @@ -0,0 +1,191 @@ +#include "tools.h" + +typedef enum { MODE_NONE, MODE_CREATE, MODE_EXTRACT } MODE; +bool DOT_FILES = false; + +internal void +NEST_usage(ENGINE* engine) { + ENGINE_printLog(engine, "\nUsage: \n"); + ENGINE_printLog(engine, " dome nest [options] [--] ( | ) ... \n"); + ENGINE_printLog(engine, "\nOptions: \n"); + ENGINE_printLog(engine, " -c --create Create a bundle from files.\n"); + ENGINE_printLog(engine, " -h --help Show this help message.\n"); + ENGINE_printLog(engine, " -o FILE, --output=FILE The file to bundle into.\n"); + ENGINE_printLog(engine, " --include-dot-files Include dot-prefixed files in bundle.\n"); + ENGINE_printLog(engine, "\n"); +} + +internal int +NEST_writeFile(ENGINE* engine, mtar_t* tar, char* filePath, char* tarPath) { + size_t length; + char* inputFile = readEntireFile(filePath, &length, NULL); + if (inputFile == NULL) { + return EXIT_FAILURE; + } + if (tarPath == NULL) { + tarPath = filePath; + } + ENGINE_printLog(engine, "Bundling: %s\n", filePath); + mtar_write_file_header(tar, tarPath, length); + mtar_write_data(tar, inputFile, length); + free(inputFile); + return EXIT_SUCCESS; +} + +internal int +NEST_packDirectory(ENGINE* engine, mtar_t* tar, char* directory, size_t start) { + tinydir_dir dir; + tinydir_open(&dir, directory); + + while (dir.has_next) { + int result = EXIT_SUCCESS; + + tinydir_file file; + tinydir_readfile(&dir, &file); + + result = EXIT_SUCCESS; + if ((strcmp(file.name, ".") != 0) && (strcmp(file.name, "..") != 0)) { + char path[PATH_MAX]; + + strcpy(path, directory); + strcat(path, "/"); + strcat(path, file.name); + + if (!DOT_FILES && strncmp(file.name, ".", 1) == 0) { + tinydir_next(&dir); + continue; + } +#if !defined(_WIN32) || !defined(__MINGW32__) + struct stat info; + lstat(path, &info); + if (S_ISLNK(info.st_mode)) { + ENGINE_printLog(engine, "Skipping symlink: %s\n", path); + tinydir_next(&dir); + continue; + } +#endif + + if (file.is_dir) { + result = NEST_packDirectory(engine, tar, path, start); + } else { + result = NEST_writeFile(engine, tar, path, path + start); + } + } + if (result == EXIT_FAILURE) { + tinydir_close(&dir); + return EXIT_FAILURE; + } + tinydir_next(&dir); + } + tinydir_close(&dir); + + return EXIT_SUCCESS; +} + + +internal int +NEST_perform(ENGINE* engine, char **argv) { + struct optparse options; + optparse_init(&options, argv); + options.permute = 0; + struct optparse_long longopts[] = { + {"create", 'c', OPTPARSE_NONE}, + {"output", 'o', OPTPARSE_REQUIRED}, + {"extract", 'x', OPTPARSE_NONE}, + {"help", 'h', OPTPARSE_NONE}, + {"include-dot-files", 128, OPTPARSE_NONE}, + {0} + }; + int option; + MODE mode = MODE_NONE; + char* outputFileName = "game.egg"; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 128: DOT_FILES = true; break; + case 'h': + printTitle(engine); + NEST_usage(engine); + return EXIT_SUCCESS; + case 'x': + if (mode != MODE_NONE) { + return EXIT_FAILURE; + } + mode = MODE_EXTRACT; + break; + case 'c': + if (mode != MODE_NONE) { + return EXIT_FAILURE; + } + mode = MODE_CREATE; + break; + case 'o': + outputFileName = strdup(options.optarg); + break; + case '?': + ENGINE_printLog(engine, "%s: %s\n", argv[0], options.errmsg); + return EXIT_FAILURE; + } + } + char** baseArg = argv + options.optind - 1; + argv += options.optind; + + // Compute the number of things we were told to pack + char* arg; + size_t argc = 0; + while ((arg = optparse_arg(&options)) != NULL) { + argc++; + } + // Reset parser + optparse_init(&options, baseArg); + options.permute = 0; + + // For now, we only support ZIP mode so we default to it. + if (mode == MODE_NONE) { + mode = MODE_CREATE; + } + + bool singleFile = (argc == 1); + if (mode == MODE_CREATE) { + mtar_t tar; + mtar_open(&tar, outputFileName, "w"); + char* path = NULL; + while ((path = optparse_arg(&options))) { + /* Is this a file or directory? */ + struct stat info; + stat(path, &info); +#if !defined(_WIN32) || !defined(__MINGW32__) + bool isLink = S_ISLNK(info.st_mode); + if (isLink) { + ENGINE_printLog(engine, "Ignoring symlink: %s\n", path); + continue; + } +#endif + bool isFile = S_ISREG(info.st_mode); + if (isFile) { + if (!DOT_FILES && path[0] == '.' && path[1] != '.' ) { + continue; + } + /* File's get added */ + NEST_writeFile(engine, &tar, path, NULL); + } else { + /* recurse into directories */ + int last = strlen(path) - 1; + if (path[last] == '/') + { + path[last] = '\0'; + } + size_t start = singleFile ? strlen(path) + 1 : 0; + NEST_packDirectory(engine, &tar, path, start); + } + } + + /* Finalize -- this needs to be the last thing done before closing */ + mtar_finalize(&tar); + /* Close archive */ + mtar_close(&tar); + ENGINE_printLog(engine, "Created bundle %s.\n", outputFileName); + } + + + return EXIT_SUCCESS; +} diff --git a/src/tools/tools.h b/src/tools/tools.h new file mode 100644 index 00000000..7823ee95 --- /dev/null +++ b/src/tools/tools.h @@ -0,0 +1,9 @@ +#ifndef TOOLS_H +#define TOOLS_H +internal void FUSE_usage(ENGINE* engine); +internal void NEST_usage(ENGINE* engine); +internal void EMBED_usage(ENGINE* engine); +internal void printUsage(ENGINE* engine); +internal void printTitle(ENGINE* engine); + +#endif diff --git a/src/util/embed.c b/src/util/embed.c deleted file mode 100644 index 43b75066..00000000 --- a/src/util/embed.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "wrenembed.c" -#include - -int main(int argc, char* args[]) -{ - if (argc < 2) { - printf("./embed sourceFile [moduleName] [destinationFile]\n"); - printf("Not enough arguments.\n"); - return EXIT_FAILURE; - } - - return WRENEMBED_encodeAndDump(argc, args); -} - - - diff --git a/src/util/wrenembed.c b/src/util/wrenembed.c deleted file mode 100644 index 91a841b5..00000000 --- a/src/util/wrenembed.c +++ /dev/null @@ -1,151 +0,0 @@ -// Converts a Wren source file to a C include file -// Using standard IO only -#include -#include -#include - -#ifndef WRENEMBED_c -#define WRENEMBED_c - -char* WRENEMBED_readEntireFile(char* path, size_t* lengthPtr) -{ - FILE* file = fopen(path, "r"); - if (file == NULL) { - return NULL; - } - - char* source = NULL; - if (fseek(file, 0L, SEEK_END) == 0) { - - // Get the size of the file. - long bufsize = ftell(file); - - // Allocate our buffer to that size. - source = malloc(sizeof(char) * (bufsize + 1)); - - // Go back to the start of the file. - if (fseek(file, 0L, SEEK_SET) != 0) { - // Error - } - - // Read the entire file into memory. - size_t newLen = fread(source, sizeof(char), bufsize, file); - - if (ferror(file) != 0) { - fclose(file); - return NULL; - } - - if (lengthPtr != NULL) { - *lengthPtr = newLen; - } - - // Add NULL, Just to be safe. - source[newLen++] = '\0'; - } - - fclose(file); - return source; -} - -int WRENEMBED_encodeAndDump(int argc, char* args[]) -{ - if (argc < 2) { - fputs("Not enough arguments\n", stderr); - return EXIT_FAILURE; - } - - size_t length; - char* fileName = args[1]; - char* fileToConvert = WRENEMBED_readEntireFile(fileName, &length); - - if (fileToConvert == NULL) { - fputs("Error reading file\n", stderr); - return EXIT_FAILURE; - } - - // TODO: Maybe use the filename as a default identifier - char* moduleName = "wren_module_test"; - - if(argc > 2) { - // TODO: Maybe sanitize moduleName to be valid C identifier? - moduleName = args[2]; - } - - FILE *fp; - if(argc > 3) { - fp = fopen(args[3], "w+"); - } else { - // Example: main.wren.inc - fp = fopen(strcat(strdup(fileName), ".inc"), "w+"); - } - - fputs("// auto-generated file, do not modify\n", fp); - fputs("const char ", fp); - fputs(moduleName, fp); - fputs("[", fp); - fprintf(fp, "%li", length + 1); - fputs("] = {", fp); - - // Encode chars - for (size_t i = 0; i < length; i++ ) { - char* ptr = fileToConvert + i; - if (*ptr == '\n') { - fputs("'\\n',", fp); - fputs("\n", fp); - } else { - fputs("'", fp); - - // TODO: Properly test the encoding with different source files - if (*ptr == '\'') { - fputs("\\\'", fp); - } else if (*ptr == '\\') { - fputs("\\\\", fp); - } else { - fwrite(ptr, sizeof(char), 1, fp); - } - - fputs("', ", fp); - } - } - - fputs("\0", fp); - fputs(" };\n", fp); - - fclose(fp); - free(fileToConvert); - - return EXIT_SUCCESS; -} - -void WRENEMBED_usage() { - fputs("dome -e | --embed sourceFile [moduleName] [destinationFile]\n", stderr); -} - -int WRENEMBED_encodeAndDumpInDOME(int argc, char* args[]) -{ - // Function to be used inside DOME that adapts argc and args - // Removing the first arg. - int count = argc - 1; - - if (count < 2) { - fputs("Not enough arguments\n", stderr); - WRENEMBED_usage(); - return EXIT_FAILURE; - } - - char* argv[count]; - int index; - - for(index = 0; index < count; index++) { - argv[index] = args[index + 1]; - } - - int exit = WRENEMBED_encodeAndDump(count, argv); - if (exit == EXIT_FAILURE) { - WRENEMBED_usage(); - } - return exit; -} - -#endif diff --git a/src/vm.c b/src/vm.c index 872c62df..87a51857 100644 --- a/src/vm.c +++ b/src/vm.c @@ -79,7 +79,7 @@ VM_load_module(WrenVM* vm, const char* name) { } // This pointer becomes owned by the WrenVM and freed later. - char* file = ENGINE_readFile(engine, path, NULL); + char* file = ENGINE_readFile(engine, path, NULL, NULL); free(path); result.source = file; @@ -168,6 +168,8 @@ internal WrenVM* VM_create(ENGINE* engine) { // DOME MAP_addFunction(&engine->moduleMap, "dome", "static Process.f_exit(_)", PROCESS_exit); MAP_addFunction(&engine->moduleMap, "dome", "static Process.args", PROCESS_getArguments); + MAP_addFunction(&engine->moduleMap, "dome", "static Process.errorDialog", PROCESS_getErrorDialog); + MAP_addFunction(&engine->moduleMap, "dome", "static Process.errorDialog=(_)", PROCESS_setErrorDialog); MAP_addFunction(&engine->moduleMap, "dome", "static Window.resize(_,_)", WINDOW_resize); MAP_addFunction(&engine->moduleMap, "dome", "static Window.title=(_)", WINDOW_setTitle); MAP_addFunction(&engine->moduleMap, "dome", "static Window.vsync=(_)", WINDOW_setVsync); @@ -190,6 +192,8 @@ internal WrenVM* VM_create(ENGINE* engine) { MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_cls(_)", CANVAS_cls); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_rect(_,_,_,_,_)", CANVAS_rect); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_line(_,_,_,_,_,_)", CANVAS_line); + MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_triangle(_,_,_,_,_,_,_)", CANVAS_triangle); + MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_trianglefill(_,_,_,_,_,_,_)", CANVAS_trianglefill); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_circle(_,_,_,_)", CANVAS_circle); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_circlefill(_,_,_,_)", CANVAS_circle_filled); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_ellipse(_,_,_,_,_)", CANVAS_ellipse); @@ -200,6 +204,8 @@ internal WrenVM* VM_create(ENGINE* engine) { MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_resize(_,_,_)", CANVAS_resize); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.width", CANVAS_getWidth); MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.height", CANVAS_getHeight); + MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.offsetX", CANVAS_getOffsetX); + MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.offsetY", CANVAS_getOffsetY); MAP_lockModule(&engine->moduleMap, "graphics"); // Font @@ -285,6 +291,8 @@ internal WrenVM* VM_create(ENGINE* engine) { MAP_addFunction(&engine->moduleMap, "input", "static Mouse.hidden", MOUSE_getHidden); MAP_addFunction(&engine->moduleMap, "input", "static Mouse.relative=(_)", MOUSE_setRelative); MAP_addFunction(&engine->moduleMap, "input", "static Mouse.relative", MOUSE_getRelative); + MAP_addFunction(&engine->moduleMap, "input", "static Mouse.cursor=(_)", MOUSE_setCursor); + MAP_addFunction(&engine->moduleMap, "input", "static Mouse.cursor", MOUSE_getCursor); MAP_addFunction(&engine->moduleMap, "input", "static SystemGamePad.f_getGamePadIds()", GAMEPAD_getGamePadIds); MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.getTrigger(_)", GAMEPAD_getTrigger); MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.close()", GAMEPAD_close); @@ -321,13 +329,21 @@ internal WrenVM* VM_create(ENGINE* engine) { MAP_addClass(&engine->moduleMap, "random", "Squirrel3", RANDOM_allocate, RANDOM_finalize); MAP_addFunction(&engine->moduleMap, "random", "static Squirrel3.noise(_,_)", RANDOM_noise); MAP_addFunction(&engine->moduleMap, "random", "Squirrel3.float()", RANDOM_float); + + MAP_addClass(&engine->moduleMap, "random", "Squirrel5", SQUIRREL5_allocate, SQUIRREL5_finalize); + MAP_addFunction(&engine->moduleMap, "random", "static Squirrel5.noise(_,_)", SQUIRREL5_noise); + MAP_addFunction(&engine->moduleMap, "random", "Squirrel5.float()", SQUIRREL5_float); MAP_lockModule(&engine->moduleMap, "random"); + engine->vm = vm; + return vm; } internal void VM_free(WrenVM* vm) { if (vm != NULL) { + ENGINE* engine = wrenGetUserData(vm); + engine->vm = NULL; wrenFreeVM(vm); } }