diff options
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/c/simple_audio.c | 40 | ||||
| -rw-r--r-- | src/main/c/simple_audio.h | 16 | ||||
| -rw-r--r-- | src/main/c/simple_graphics.c | 60 | ||||
| -rw-r--r-- | src/main/c/simple_graphics.h | 19 | ||||
| -rw-r--r-- | src/main/c/simple_miniaudio.c | 123 | ||||
| -rw-r--r-- | src/main/c/simple_minifb.c | 113 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/README.md | 2 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/codegen/Serialize.java | 12 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/node/LoadNode.java | 16 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java | 5 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/parser/Expressions.java | 2 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/parser/Lexer.java | 26 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/parser/README.md | 2 | ||||
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/parser/package-info.java | 2 | ||||
| -rw-r--r-- | src/main/smp/sys.smp | 3 | ||||
| -rw-r--r-- | src/main/smp/sys/audio.smp | 29 | ||||
| -rw-r--r-- | src/main/smp/sys/draw.smp | 17 | ||||
| -rw-r--r-- | src/main/smp/sys/draw/Canvas.smp | 176 | ||||
| -rw-r--r-- | src/main/smp/sys/gfx.smp | 38 |
19 files changed, 683 insertions, 18 deletions
diff --git a/src/main/c/simple_audio.c b/src/main/c/simple_audio.c new file mode 100644 index 00000000..b8ed62de --- /dev/null +++ b/src/main/c/simple_audio.c @@ -0,0 +1,40 @@ +#include "simple_audio.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dlfcn.h> + +static const struct simple_audio_api *api; +static void *library; + +int32_t simple_audio_open(void) { + const char *path = getenv("SIMPLE_MINIAUDIO"); + if (path && strcmp(path,"off")==0) return -1; + if (!api) { + if (!path || !*path) return -1; // Optional: games may run silently. + library = dlopen(path,RTLD_NOW | RTLD_LOCAL); + if (!library) { + fprintf(stderr,"Simple audio: cannot load backend '%s': %s\n",path,dlerror()); + return -1; + } + const struct simple_audio_api *(*get_api)(void) = + (const struct simple_audio_api *(*)(void))dlsym(library,"simple_miniaudio_api"); + const struct simple_audio_api *candidate = get_api ? get_api() : NULL; + if (!candidate || candidate->version!=SIMPLE_AUDIO_VERSION || !candidate->open || + !candidate->load || !candidate->play || !candidate->stop || + !candidate->playing || !candidate->close) { + fprintf(stderr,"Simple audio: incompatible backend; rebuild with make audio.\n"); + dlclose(library); + library=NULL; + return -1; + } + api=candidate; + atexit(api->close); + } + return api->open(); +} +int32_t simple_audio_load(const int16_t *samples, uint32_t count) { return api ? api->load(samples,count) : -1; } +int32_t simple_audio_play(int32_t sound) { return api ? api->play(sound) : -1; } +int32_t simple_audio_stop(void) { return api ? api->stop() : 0; } +int32_t simple_audio_playing(void) { return api ? api->playing() : 0; } +int32_t simple_audio_close(void) { if (api) api->close(); return 0; } diff --git a/src/main/c/simple_audio.h b/src/main/c/simple_audio.h new file mode 100644 index 00000000..77cce530 --- /dev/null +++ b/src/main/c/simple_audio.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_AUDIO_H +#define SIMPLE_AUDIO_H +#include <stdint.h> + +// Main-thread API. Samples are copied; no Simple heap pointer is retained. +struct simple_audio_api { + int32_t version; + int32_t (*open)(void); + int32_t (*load)(const int16_t *, uint32_t); + int32_t (*play)(int32_t); + int32_t (*stop)(void); + int32_t (*playing)(void); + void (*close)(void); +}; +#define SIMPLE_AUDIO_VERSION 1 +#endif diff --git a/src/main/c/simple_graphics.c b/src/main/c/simple_graphics.c new file mode 100644 index 00000000..41073470 --- /dev/null +++ b/src/main/c/simple_graphics.c @@ -0,0 +1,60 @@ +#include "simple_graphics.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dlfcn.h> + +// No MiniFB headers or link dependency in the default runtime. Cygwin's +// dlopen also loads native Windows DLLs; both use the x64 Windows C ABI. +static const struct simple_graphics_api *api; +static void *library; + +static int load_graphics(void) { + // Explicitly disabling graphics also makes headless tests deterministic. + const char *path = getenv("SIMPLE_MINIFB"); + if (path && strcmp(path, "off") == 0) { + fprintf(stderr, "Simple graphics: disabled (SIMPLE_MINIFB=off).\n"); + return -1; + } + if (api) return 0; + if (!path || !*path) { + fprintf(stderr, "Simple graphics: MiniFB backend not configured. " + "Run make graphics, then set SIMPLE_MINIFB to the backend library's full path.\n"); + return -1; + } + library = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (!library) { + fprintf(stderr, "Simple graphics: cannot load MiniFB backend '%s': %s\n", path, dlerror()); + return -1; + } + const struct simple_graphics_api *(*get_api)(void) = + (const struct simple_graphics_api *(*)(void))dlsym(library, "simple_minifb_api"); + const struct simple_graphics_api *candidate = get_api ? get_api() : NULL; + if (!candidate || candidate->version != SIMPLE_GRAPHICS_VERSION || + !candidate->open || !candidate->present || !candidate->poll || + !candidate->mouse_x || !candidate->mouse_y || !candidate->button || + !candidate->key || !candidate->close) { + fprintf(stderr, "Simple graphics: incompatible MiniFB backend '%s'. Rebuild with make graphics.\n", path); + dlclose(library); + library = NULL; + return -1; + } + api = candidate; + // Keep the library loaded until process exit; its window callbacks may + // still be registered. Closing a window releases its native resources. + atexit(api->close); + return 0; +} + +int32_t simple_gfx_open(const char *title, uint32_t width, uint32_t height) { + return load_graphics() ? -1 : api->open(title, width, height); +} +int32_t simple_gfx_present(const uint32_t *pixels, uint32_t count) { + return api ? api->present(pixels, count) : -1; +} +int32_t simple_gfx_poll(int32_t wait) { return api ? api->poll(wait) : -1; } +int32_t simple_gfx_mouse_x(void) { return api ? api->mouse_x() : 0; } +int32_t simple_gfx_mouse_y(void) { return api ? api->mouse_y() : 0; } +int32_t simple_gfx_button(int32_t button) { return api ? api->button(button) : 0; } +int32_t simple_gfx_key(int32_t key) { return api ? api->key(key) : 0; } +int32_t simple_gfx_close(void) { if (api) api->close(); return 0; } diff --git a/src/main/c/simple_graphics.h b/src/main/c/simple_graphics.h new file mode 100644 index 00000000..35a0aa08 --- /dev/null +++ b/src/main/c/simple_graphics.h @@ -0,0 +1,19 @@ +#ifndef SIMPLE_GRAPHICS_H +#define SIMPLE_GRAPHICS_H +#include <stdint.h> + +// Private, versioned boundary between the tiny runtime loader and MiniFB. +// All calls run on the main thread. Pixel memory is borrowed for present only. +struct simple_graphics_api { + int32_t version; + int32_t (*open)(const char *, uint32_t, uint32_t); + int32_t (*present)(const uint32_t *, uint32_t); + int32_t (*poll)(int32_t); + int32_t (*mouse_x)(void); + int32_t (*mouse_y)(void); + int32_t (*button)(int32_t); + int32_t (*key)(int32_t); + void (*close)(void); +}; +#define SIMPLE_GRAPHICS_VERSION 1 +#endif diff --git a/src/main/c/simple_miniaudio.c b/src/main/c/simple_miniaudio.c new file mode 100644 index 00000000..03dbadd7 --- /dev/null +++ b/src/main/c/simple_miniaudio.c @@ -0,0 +1,123 @@ +#include "simple_audio.h" +#define MA_NO_DECODING +#define MA_NO_ENCODING +#define MA_NO_RESOURCE_MANAGER +#define MA_NO_NODE_GRAPH +#define MA_NO_ENGINE +#define MINIAUDIO_IMPLEMENTATION +#include <miniaudio.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#define RATE 48000 +#define SLOTS 32 +static ma_device device; +static ma_mutex mutex; +static int opened, active; +static uint32_t cursor; +static struct { int16_t *pcm; uint32_t count; } sounds[SLOTS]; + +// Only native-owned immutable PCM is read on the audio thread. The lock covers +// a bounded copy, never allocation, synthesis, device calls, or Simple code. +static void output(ma_device *unused, void *out, const void *in, ma_uint32 frames) { + (void)unused; (void)in; + memset(out,0,frames*sizeof(int16_t)); + ma_mutex_lock(&mutex); + if (active) { + uint32_t count=sounds[active].count-cursor; + if (count>frames) count=frames; + memcpy(out,sounds[active].pcm+cursor,count*sizeof(int16_t)); + cursor+=count; + if (cursor==sounds[active].count) active=0; + } + ma_mutex_unlock(&mutex); +} +static int32_t load(const int16_t *pcm, uint32_t count) { + if (!opened || !pcm || !count || count>RATE*10) return -1; + int slot; + for (slot=4; slot<SLOTS && sounds[slot].pcm; ++slot) {} + if (slot==SLOTS) return -1; + int16_t *copy=malloc(count*sizeof(*copy)); + if (!copy) return -1; + memcpy(copy,pcm,count*sizeof(*copy)); + ma_mutex_lock(&mutex); + sounds[slot].pcm=copy; sounds[slot].count=count; + ma_mutex_unlock(&mutex); + return slot; +} +static int32_t play(int32_t sound) { + if (!opened || sound<=0 || sound>=SLOTS || !sounds[sound].pcm) return -1; + ma_mutex_lock(&mutex); + active=sound; cursor=0; + ma_mutex_unlock(&mutex); + return 0; +} +static int32_t stop(void) { + if (opened) { ma_mutex_lock(&mutex); active=0; ma_mutex_unlock(&mutex); } + return 0; +} +static int32_t playing(void) { + if (!opened) return 0; + ma_mutex_lock(&mutex); + int result=active!=0; + ma_mutex_unlock(&mutex); + return result; +} +static void close_audio(void) { + if (!opened) return; + ma_device_uninit(&device); // Joins callback before freeing buffers/mutex. + ma_mutex_uninit(&mutex); + for (int i=1;i<SLOTS;++i) { + free(sounds[i].pcm); sounds[i].pcm=NULL; sounds[i].count=0; + } + opened=0; active=0; cursor=0; +} +static int tone(int slot, int descending) { + uint32_t note=slot==1 ? RATE*7/100 : RATE*16/100; + int notes=slot==1 ? 1 : 3; + sounds[slot].count=note*notes; + sounds[slot].pcm=calloc(note*notes,sizeof(int16_t)); + if (!sounds[slot].pcm) return -1; + const double pitches[3]={523.251,659.255,783.991}; + for (int n=0;n<notes;++n) { + double hz=slot==1 ? 440.0 : pitches[descending ? 2-n : n]; + for (uint32_t i=0;i<note;++i) { + double t=(double)i/note; + double envelope=sin(3.141592653589793*t); + sounds[slot].pcm[n*note+i]=(int16_t)(3200*envelope*envelope*sin(6.283185307179586*hz*i/RATE)); + } + } + return 0; +} +static int32_t open_audio(void) { + if (opened) return 0; + if (ma_mutex_init(&mutex)!=MA_SUCCESS) return -1; + ma_device_config config=ma_device_config_init(ma_device_type_playback); + config.playback.format=ma_format_s16; + config.playback.channels=1; + config.sampleRate=RATE; + config.dataCallback=output; +#ifdef SIMPLE_AUDIO_TEST_NULL + ma_backend backend=ma_backend_null; + ma_result result=ma_device_init_ex(&backend,1,NULL,&config,&device); +#else + ma_result result=ma_device_init(NULL,&config,&device); +#endif + if (result!=MA_SUCCESS) { + ma_mutex_uninit(&mutex); + fprintf(stderr,"Simple audio: no playback device (%d); continuing silently.\n",result); + return -1; + } + opened=1; + if (tone(1,0) || tone(2,0) || tone(3,1) || ma_device_start(&device)!=MA_SUCCESS) { + close_audio(); return -1; + } + return 0; +} +static const struct simple_audio_api api={SIMPLE_AUDIO_VERSION,open_audio,load,play,stop,playing,close_audio}; +#ifdef _WIN32 +__declspec(dllexport) +#endif +const struct simple_audio_api *simple_miniaudio_api(void) { return &api; } diff --git a/src/main/c/simple_minifb.c b/src/main/c/simple_minifb.c new file mode 100644 index 00000000..34663e2e --- /dev/null +++ b/src/main/c/simple_minifb.c @@ -0,0 +1,113 @@ +#include "simple_graphics.h" +#include <MiniFB.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef _WIN32 +#include <windows.h> +#else +#include <errno.h> +#include <time.h> +#endif + +// One fixed-size window for now. No Simple pointer survives a native call: +// MiniFB retains its last draw buffer for OS repaint events, so copy it here. +static struct mfb_window *window; +static uint32_t *frame; +static uint32_t width, height; + +// The C-only build never installs the upstream C++ callback wrappers. +void release_cpp_stub(struct mfb_window *unused) { (void)unused; } + +static void close_window(void) { + if (window) { + mfb_close(window); // Requests close. + mfb_update_events(window); // Actually destroys the window. + window = NULL; + } + free(frame); + frame = NULL; +} + +static int32_t open_window(const char *title, uint32_t w, uint32_t h) { + if (window || !w || !h || w > UINT32_MAX / 4 || + (uint64_t)w * h > UINT32_MAX || (uint64_t)w * h > SIZE_MAX / 4) { + fprintf(stderr, "Simple graphics: invalid dimensions or a window is already open.\n"); + return -1; + } + frame = calloc((size_t)w * h, sizeof(*frame)); + if (!frame) { + fprintf(stderr, "Simple graphics: cannot allocate the display buffer.\n"); + return -1; + } + window = mfb_open(title, w, h); + if (!window) { + fprintf(stderr, "Simple graphics: MiniFB could not open a window (is a graphical display available?).\n"); + close_window(); + return -1; + } + width = w; + height = h; + return 0; +} + +static int32_t status(mfb_update_state state) { + if (state == MFB_STATE_OK) return 0; + if (state == MFB_STATE_EXIT) { + window = NULL; // MiniFB has already destroyed it. + close_window(); + return 1; + } + fprintf(stderr, "Simple graphics: MiniFB update failed (%d).\n", state); + close_window(); + return -1; +} + +static int32_t present(const uint32_t *pixels, uint32_t count) { + if (!window) return 1; + if (!pixels || count != (uint64_t)width * height) { + fprintf(stderr, "Simple graphics: pixel array length must equal width * height.\n"); + return -1; + } + memcpy(frame, pixels, (size_t)count * sizeof(*frame)); + return status(mfb_update_ex(window, frame, width, height)); +} + +static int32_t poll_events(int32_t wait) { + if (!window) return 1; + if (wait) { + // mfb_wait_sync returns false for both destruction and errors that + // leave the window alive. Use a short OS wait, then the status-bearing + // event API, so we know whether the window still needs destruction. +#ifdef _WIN32 + Sleep(17); +#else + struct timespec delay = {0, 16666667}; + while (nanosleep(&delay, &delay) < 0) { + if (errno != EINTR) { + perror("Simple graphics: event wait"); + close_window(); + return -1; + } + } +#endif + } + return status(mfb_update_events(window)); +} +static int32_t mouse_x(void) { return window ? mfb_get_mouse_x(window) : 0; } +static int32_t mouse_y(void) { return window ? mfb_get_mouse_y(window) : 0; } +static int32_t button(int32_t b) { + return window && b >= 0 && b < 8 ? mfb_get_mouse_button_buffer(window)[b] != 0 : 0; +} +static int32_t key(int32_t k) { + return window && k >= 0 && k <= MFB_KB_KEY_LAST ? mfb_get_key_buffer(window)[k] != 0 : 0; +} + +static const struct simple_graphics_api api = { + SIMPLE_GRAPHICS_VERSION, open_window, present, poll_events, + mouse_x, mouse_y, button, key, close_window +}; +#ifdef _WIN32 +__declspec(dllexport) +#endif +const struct simple_graphics_api *simple_minifb_api(void) { return &api; } diff --git a/src/main/java/com/seaofnodes/simple/README.md b/src/main/java/com/seaofnodes/simple/README.md index af86f21e..a8916aa8 100644 --- a/src/main/java/com/seaofnodes/simple/README.md +++ b/src/main/java/com/seaofnodes/simple/README.md @@ -2,7 +2,7 @@ | Directory | Contents | | --- | --- | -| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter30/30-grammar.md). | +| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter31/31-grammar.md). | | `node/` | Sea-of-Nodes IR nodes: graph edges, type computation, and local peephole rewrites. | | `node/cpus/` | Machine-specific nodes and instruction selection support for `arm`, `riscv`, and `x86_64_v2`. | | `codegen/` | Compilation driver and passes: module loading, optimization, scheduling, register allocation, serialization, and machine-code emission. `IterPeeps` runs the peephole worklist. | diff --git a/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java index ae4ecb74..79bebe1f 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Serialize.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java @@ -417,7 +417,7 @@ public class Serialize { assert elf._clz._name==strs[1/*skip null ptr*/]; for( Type type : types ) if( type instanceof TypeStruct ts ) - Parser.TYPES.put(ts._name,ts); + installType(ts); for( Type type : types ) { if( !(type instanceof TypeStruct ts) || !TypeStruct.startsClzPrefix(ts._name) ) continue; @@ -429,7 +429,7 @@ public class Serialize { elf._pubTypes = new TypeStruct[npubTypes]; for( int i=0; i<npubTypes; i++ ) { TypeStruct ts = elf._pubTypes[i] = (TypeStruct)types[pubTypeIds[i]]; - Parser.TYPES.put(ts._name,ts); + installType(ts); } elf._functions = new FunctionSummary[bais.packed4()]; for( int i = 0; i<elf._functions.length; i++ ) { @@ -444,6 +444,14 @@ public class Serialize { return elf._types = new TypeRead(types,strs,fileAliases,fileFidxs,fileRpcs); } + private static void installType(TypeStruct ts) { + // TYPES names the mutable array declaration. Read-only arrays in + // imported signatures keep their own types, but must not replace that + // declaration: closeOver looks up its mutable variant by name. + if( ts.isAry() ) ts = ts.replace(ts.field("[]").makeFrom(false)); + Parser.TYPES.put(ts._name,ts); + } + static void readAll( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) { BAOS bais = elf._bais; TypeRead tread = readTypes(code, elf, aliases, fidxs, rpcs); diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index eb78ed1f..35db4c2f 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -364,15 +364,13 @@ public class LoadNode extends MemOpNode { if( ptr instanceof PhiNode pphi && pphi.region() == r ) return true; - // No, so can we lift this ptr? - CFGNode cptr = ptr.cfg0(); - if( cptr != null ) - // Pointer is controlled high - // TODO: Really needs to be the LCA of all inputs is high - return cptr.idepth() <= r.idepth(); - - // Dunno without a longer walk - return false; + // Each copied Load needs its pointer before the Region, including + // a loop's entry arm. input 0 alone misses floating Loads whose own + // pointer was produced inside the loop. + CFGNode pinned = ptr.cfg0(); + if( pinned==null || pinned.idepth()>r.idepth() ) return false; + CFGNode cptr = ptr instanceof PhiNode phi ? phi.region() : CFGNode.earlyCFG(ptr); + return cptr==null || cptr.dominates(r.idom(memphi),memphi); } @Override public Parser.ParseException err() { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 3d71b4ea..673339a9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -370,8 +370,9 @@ public class x86_64_v2 extends Machine { private Node _lea(Node add, Node base, Node idx, int off) { int scale = 0; - if( base instanceof ShlNode && !(idx instanceof ShlNode) ) - throw Utils.TODO(); // Bug in canonicalization, should on RHS + if( base instanceof ShlNode && !(idx instanceof ShlNode) ) { + Node tmp = base; base = idx; idx = tmp; + } if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { idx = shift.in(1); diff --git a/src/main/java/com/seaofnodes/simple/parser/Expressions.java b/src/main/java/com/seaofnodes/simple/parser/Expressions.java index 247d974e..6486aba8 100644 --- a/src/main/java/com/seaofnodes/simple/parser/Expressions.java +++ b/src/main/java/com/seaofnodes/simple/parser/Expressions.java @@ -400,7 +400,7 @@ final class Expressions { * Parse integer literal * * <pre> - * integerLiteral: [1-9][0-9]* | [0] + * integerLiteral: [1-9][0-9]* | 0[0-7]* | 0[xX][0-9a-fA-F]+ * floatLiteral: [digits].[digits]?[e [digits]]?[f]? * </pre> */ diff --git a/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/src/main/java/com/seaofnodes/simple/parser/Lexer.java index 5e4b7311..9ab678ca 100644 --- a/src/main/java/com/seaofnodes/simple/parser/Lexer.java +++ b/src/main/java/com/seaofnodes/simple/parser/Lexer.java @@ -239,6 +239,7 @@ public class Lexer { // Return a constant Type, either TypeInteger or TypeFloat Type parseNumber() { Lexer start=new Lexer(this); + if( isHex() ) return parseInteger(parseNumberString(),16,2,start); int old = _position; int len = isLongOrDouble(); boolean f32 = peek()=='f'; @@ -250,7 +251,7 @@ public class Lexer { return TypeFloat.constant(Float.parseFloat(literal)); if( len > 0 ) { if( len > 1 && _input[old]=='0' ) - throw Parser.error("Syntax error: integer values cannot start with '0'",start); + return parseInteger(literal,8,0,start); long i = Long.parseLong(literal); return TypeInteger.constant(i); } @@ -262,6 +263,11 @@ public class Lexer { } private String parseNumberString() { int old = _position; + if( isHex() ) { + _position += 2; + while( isIdLetter(peek()) ) inc(); + return new String(_input,old,_position-old); + } int len = Math.abs(isLongOrDouble()); if( peek()=='f' ) { nextChar(); @@ -270,6 +276,24 @@ public class Lexer { return new String(_input,old,len); } + private boolean isHex() { + return peek()=='0' && _position+1<_input.length && (peek(1)=='x' || peek(1)=='X'); + } + + private TypeInteger parseInteger(String literal, int radix, int prefix, Lexer start) { + String kind = radix==16 ? "Hexadecimal" : "Octal"; + if( literal.length()==prefix ) + throw Parser.error(kind+" integer literal requires at least one digit",start); + for( int i=prefix; i<literal.length(); i++ ) + if( Character.digit(literal.charAt(i),radix)<0 ) + throw Parser.error("Invalid "+kind.toLowerCase()+" integer literal '"+literal+"'",start); + try { + return TypeInteger.constant(Long.parseUnsignedLong(literal.substring(prefix),radix)); + } catch( NumberFormatException ex ) { + throw Parser.error(kind+" integer literal exceeds 64 bits",start); + } + } + // Return +len that ends a long // Return -len that ends a double private int isLongOrDouble() { diff --git a/src/main/java/com/seaofnodes/simple/parser/README.md b/src/main/java/com/seaofnodes/simple/parser/README.md index cee1d64a..d0220761 100644 --- a/src/main/java/com/seaofnodes/simple/parser/README.md +++ b/src/main/java/com/seaofnodes/simple/parser/README.md @@ -2,7 +2,7 @@ The parser builds Sea-of-Nodes IR directly. Each component shares the cursor, active scope, and compilation state through its `Parser P` reference; there is no intermediate -AST. The [grammar map](../../../../../../../docs/chapter30/30-grammar.md) connects language +AST. The [grammar map](../../../../../../../docs/chapter31/31-grammar.md) connects language productions to their entry points. | File | Responsibility | diff --git a/src/main/java/com/seaofnodes/simple/parser/package-info.java b/src/main/java/com/seaofnodes/simple/parser/package-info.java index c886f72a..73ebec2e 100644 --- a/src/main/java/com/seaofnodes/simple/parser/package-info.java +++ b/src/main/java/com/seaofnodes/simple/parser/package-info.java @@ -7,7 +7,7 @@ * use-side name/field resolution; Allocations, ControlFlow, and Conversions * contain their shared IR construction. Lexer also supplies location snapshots. * - * <p>See {@code chapter30/docs/30-grammar.md} for the production-to-method map. + * <p>See {@code chapter31/docs/31-grammar.md} for the production-to-method map. * Evaluation order and scope lifetimes are explicit: loop updates and declared * constructor bodies are the two deferred source fragments. There is no AST. */ diff --git a/src/main/smp/sys.smp b/src/main/smp/sys.smp index 1201315b..40a4b955 100644 --- a/src/main/smp/sys.smp +++ b/src/main/smp/sys.smp @@ -2,11 +2,14 @@ // top-level default standard library import val adt = adt; // Abstract Data Types +val audio = audio; // Optional background sound (miniaudio) val ary = ary; // Array utilities val aryi64 = aryi64; // Variable sized array of i64 val aryu8 = aryu8; // Variable sized array of u8 val char = char; // Character utilities val io = io; // IO library +val gfx = gfx; // Optional pixel window (MiniFB) +val draw = draw; // Software raster drawing in Simple val text = text; // Typed text conversions val libc = libc; // libc FFI bindings val rt = rt; // Run Time support diff --git a/src/main/smp/sys/audio.smp b/src/main/smp/sys/audio.smp new file mode 100644 index 00000000..89e6dfa9 --- /dev/null +++ b/src/main/smp/sys/audio.smp @@ -0,0 +1,29 @@ +// Optional background audio. All API calls belong on the game/main thread. +int OK = 0; +int ERROR = -1; +int BOOP = 1; +int WIN = 2; +int LOSE = 3; +int RATE = 48000; +{ i32 } simple_audio_open = "C"; +{ i64 u32 -> i32 } simple_audio_load = "C"; +{ i32 -> i32 } simple_audio_play = "C"; +{ i32 } simple_audio_stop = "C"; +{ i32 } simple_audio_playing = "C"; +{ i32 } simple_audio_close = "C"; +// Missing/disabled backend returns ERROR quietly; the game may remain silent. +val open = { -> simple_audio_open(); }; +// Mono signed 16-bit PCM at RATE, up to ten seconds. Copies into native memory. +// Load at startup: up to 28 custom handles, valid until close, then reload. +val load = { i16[~] samples -> + i64 ptr = samples; + simple_audio_load(ptr,samples#); +}; +// Starts/restarts immediately, replacing the previous sound; never waits for it. +val play = { int sound -> simple_audio_play(sound); }; +val boop = { -> simple_audio_play(BOOP); }; +val win = { -> simple_audio_play(WIN); }; +val lose = { -> simple_audio_play(LOSE); }; +val stop = { -> simple_audio_stop(); }; +val playing = { -> simple_audio_playing(); }; +val close = { -> simple_audio_close(); }; diff --git a/src/main/smp/sys/draw.smp b/src/main/smp/sys/draw.smp new file mode 100644 index 00000000..0721659a --- /dev/null +++ b/src/main/smp/sys/draw.smp @@ -0,0 +1,17 @@ +// Software raster drawing. No window or native calls; pixels are packed RGB (0x00RRGGBB). +u32 BLACK = 0x000000; +u32 WHITE = 0xffffff; +u32 RED = 0xe05252; +u32 GREEN = 0x48b88c; +u32 BLUE = 0x558ee6; + +// Components are clamped to bytes rather than wrapping. +val rgb = { int r, int g, int b -> + r = r<0 ? 0 : r>255 ? 255 : r; + g = g<0 ? 0 : g>255 ? 255 : g; + b = b<0 ? 0 : b>255 ? 255 : b; + u32 color = (r<<16) | (g<<8) | b; + return color; +}; + +val Canvas = Canvas; // Pixel canvas and drawing methods diff --git a/src/main/smp/sys/draw/Canvas.smp b/src/main/smp/sys/draw/Canvas.smp new file mode 100644 index 00000000..8c01b4b3 --- /dev/null +++ b/src/main/smp/sys/draw/Canvas.smp @@ -0,0 +1,176 @@ +struct Canvas { + u32 width; + u32 height; + u32[] pixels; + + new Canvas = { int w, int h -> + i64 count = w; + count *= h; + if( w<=0 || h<=0 || count>2147483647 ) libc._exit(1); + width = w; + height = h; + pixels = new u32[count]; + }; + + val clear = { u32 color -> + for( int i=0; i<pixels#; i++ ) pixels[i] = color; + return self; + }; + + val pixel = { i64 x, i64 y, u32 color -> + if( 0<=x<width && 0<=y<height ) pixels[y*width+x] = color; + return self; + }; + + // Integer coordinates, top-left origin. Rectangle extents are half-open. + val fillRect = { i64 x, i64 y, i64 w, i64 h, u32 color -> + if( w<=0 || h<=0 ) return self; + i64 right = x; right += w; + i64 bottom = y; bottom += h; + if( right<=0 || bottom<=0 || x>=width || y>=height ) return self; + int x0 = x<0 ? 0 : x, y0 = y<0 ? 0 : y; + int x1 = right>width ? width : right, y1 = bottom>height ? height : bottom; + for( int py=y0; py<y1; py++ ) + for( int px=x0; px<x1; px++ ) pixels[py*width+px] = color; + return self; + }; + + // Outline stays inside the rectangle. Wide borders become a filled rect. + val rect = { i64 x, i64 y, i64 w, i64 h, i64 thickness, u32 color -> + if( w<=0 || h<=0 || thickness<=0 ) return self; + if( thickness>=w || thickness>=h || thickness>=w-thickness || thickness>=h-thickness ) + return fillRect(x,y,w,h,color); + fillRect(x,y,w,thickness,color); + fillRect(x,y+h-thickness,w,thickness,color); + fillRect(x,y+thickness,thickness,h-2*thickness,color); + return fillRect(x+w-thickness,y+thickness,thickness,h-2*thickness,color); + }; + + // Integer centers are pixel centers. Circle outlines extend inward. + val fillCircle = { i64 cx, i64 cy, int radius, u32 color -> + return _circle(cx,cy,radius,0,true,color); + }; + val circle = { i64 cx, i64 cy, int radius, int thickness, u32 color -> + return _circle(cx,cy,radius,thickness,false,color); + }; + val _circle = { i64 cx, i64 cy, int radius, int thickness, bool filled, u32 color -> + if( radius<0 || (!filled && thickness<=0) ) return self; + i64 left = cx; left -= radius; + i64 top = cy; top -= radius; + i64 right = cx; right += radius; + i64 bottom = cy; bottom += radius; + if( right<0 || bottom<0 || left>=width || top>=height ) return self; + int x0 = left<0 ? 0 : left, y0 = top<0 ? 0 : top; + int x1 = right>=width ? width-1 : right, y1 = bottom>=height ? height-1 : bottom; + i64 outer2 = radius; + outer2 *= radius; + i64 inner = filled ? -1 : radius-thickness; + for( int py=y0; py<=y1; py++ ) + for( int px=x0; px<=x1; px++ ) { + i64 dx = px; dx -= cx; + i64 dy = py; dy -= cy; + i64 d2 = dx*dx+dy*dy; + if( d2<=outer2 && (inner<=0 || d2>inner*inner) ) pixels[py*width+px] = color; + } + return self; + }; + + // Round caps; a zero-length segment is a dot. Only the clipped bounding + // box is scanned. Floating distances avoid overflowing coordinate squares. + val line = { i64 x0, i64 y0, i64 x1, i64 y1, i64 thickness, u32 color -> + if( thickness<=0 ) return self; + i64 pad = thickness/2+1; + i64 left = x0<x1 ? x0 : x1; left -= pad; + i64 top = y0<y1 ? y0 : y1; top -= pad; + i64 right = x0>x1 ? x0 : x1; right += pad; + i64 bottom = y0>y1 ? y0 : y1; bottom += pad; + if( right<0 || bottom<0 || left>=width || top>=height ) return self; + int bx0 = left<0 ? 0 : left, by0 = top<0 ? 0 : top; + int bx1 = right>=width ? width-1 : right, by1 = bottom>=height ? height-1 : bottom; + for( int py=by0; py<=by1; py++ ) + for( int px=bx0; px<=bx1; px++ ) + if( _onLine(px,py,x0,y0,x1,y1,thickness) ) pixels[py*width+px] = color; + return self; + }; + + // Small 3x5 bitmap font for game labels. Lowercase maps to uppercase. + // Newline returns to x; unsupported characters draw a question mark. + val text = { i64 x, i64 y, u8[~] str, int scale, u32 color -> + if( scale<=0 ) return self; + i64 px = x, py = y; + for( int i=0; i<str#; i++ ) { + u8 c = str[i]; + if( c=='\n' ) { px=x; py+=6*scale; continue; } + int bits = _glyph(c); + for( int row=0; row<5; row++ ) + for( int col=0; col<3; col++ ) + if( (bits >> (14-row*3-col)) & 1 ) + fillRect(px+col*scale,py+row*scale,scale,scale,color); + px += 4*scale; + } + return self; + }; +}; + +val _glyph = { u8 c -> + // Five octal digits: one 3-bit row per digit, top to bottom. + if( 'a'<=c<='z' ) c -= 'a'-'A'; + if( c==' ' ) return 000000; + if( c=='0' ) return 075557; + if( c=='1' ) return 026227; + if( c=='2' ) return 061247; + if( c=='3' ) return 061216; + if( c=='4' ) return 055711; + if( c=='5' ) return 074616; + if( c=='6' ) return 034757; + if( c=='7' ) return 071222; + if( c=='8' ) return 075757; + if( c=='9' ) return 075716; + if( c=='A' ) return 025755; + if( c=='B' ) return 065656; + if( c=='C' ) return 034443; + if( c=='D' ) return 065556; + if( c=='E' ) return 074647; + if( c=='F' ) return 074644; + if( c=='G' ) return 034553; + if( c=='H' ) return 055755; + if( c=='I' ) return 072227; + if( c=='J' ) return 011152; + if( c=='K' ) return 055655; + if( c=='L' ) return 044447; + if( c=='M' ) return 057755; + if( c=='N' ) return 057775; + if( c=='O' ) return 025552; + if( c=='P' ) return 065644; + if( c=='Q' ) return 025573; + if( c=='R' ) return 065655; + if( c=='S' ) return 034216; + if( c=='T' ) return 072222; + if( c=='U' ) return 055557; + if( c=='V' ) return 055552; + if( c=='W' ) return 055775; + if( c=='X' ) return 055255; + if( c=='Y' ) return 055222; + if( c=='Z' ) return 071247; + if( c=='-' ) return 000700; + if( c=='.' ) return 000002; + if( c==':' ) return 002020; + if( c=='!' ) return 022202; + if( c=='?' ) return 061202; + return 061202; +}; + +// Pixel coverage for a round-ended segment, separate from traversal. +val _onLine = { i64 px, i64 py, i64 x0, i64 y0, i64 x1, i64 y1, i64 thickness -> + f64 dx = x1; dx -= x0; + f64 dy = y1; dy -= y0; + f64 vx = px; vx -= x0; + f64 vy = py; vy -= y0; + f64 length2 = dx*dx+dy*dy; + bool point = x0==x1 && y0==y1; + f64 t = point ? 0.0 : (vx*dx+vy*dy)/length2; + t = t<0 ? 0.0 : t>1 ? 1.0 : t; + f64 ex = vx-t*dx, ey = vy-t*dy; + f64 half = thickness/2.0; + return ex*ex+ey*ey<=half*half; +}; diff --git a/src/main/smp/sys/gfx.smp b/src/main/smp/sys/gfx.smp new file mode 100644 index 00000000..1f0e3ca9 --- /dev/null +++ b/src/main/smp/sys/gfx.smp @@ -0,0 +1,38 @@ +// Minimal pixel-window interface; drawing belongs in Simple. +// One fixed-size window, main thread only. Pixel format is 0x00RRGGBB. +// open/present/poll return OK, CLOSED (normal user close/Esc), or ERROR. +int OK = 0; +int CLOSED = 1; +int ERROR = -1; +int LEFT = 1; +int ESCAPE = 256; + +{ i64 u32 u32 -> i32 } simple_gfx_open = "C"; +{ i64 u32 -> i32 } simple_gfx_present = "C"; +{ i32 -> i32 } simple_gfx_poll = "C"; +{ i32 } simple_gfx_mouse_x = "C"; +{ i32 } simple_gfx_mouse_y = "C"; +{ i32 -> i32 } simple_gfx_button = "C"; +{ i32 -> i32 } simple_gfx_key = "C"; +{ i32 } simple_gfx_close = "C"; + +// Failure prints a diagnostic to stderr and returns ERROR; callers decide +// whether to exit or fall back to text. No native library is loaded until here. +val open = { u8[~] title, u32 width, u32 height -> + i64 ptr = aryu8.str0(title); + simple_gfx_open(ptr,width,height); +}; +// Array length must be width*height. The native layer copies for OS repaints; +// it never keeps a pointer into the Simple heap after this call returns. +val present = { u32[~] pixels -> + i64 ptr = pixels; + simple_gfx_present(ptr,pixels#); +}; +// wait=true waits about 17ms before polling, avoiding a busy loop. +val poll = { bool wait -> simple_gfx_poll(wait); }; +val mouseX = { -> simple_gfx_mouse_x(); }; +val mouseY = { -> simple_gfx_mouse_y(); }; +val button = { int button -> simple_gfx_button(button); }; +// Printable keys use uppercase ASCII; special keys use MiniFB's key codes. +val key = { int key -> simple_gfx_key(key); }; +val close = { -> simple_gfx_close(); }; |
