From 1c53304d03414890af0da7432d1aea87e754a14a Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Wed, 14 Oct 2026 08:29:46 +0330 Subject: feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests --- src/tools/automap.sh | 10 ++++- src/tools/build.zc | 13 +++++-- src/tools/embed.sh | 89 ++++++++++++++++++++++++++++++++++++++++++ src/tools/genmap.c | 13 ++++--- src/tools/mkgenome.c | 11 +++++- src/tools/mklsp.sh | 12 +++++- src/tools/mklspwasm.sh | 6 ++- src/tools/mkvsix.sh | 4 ++ src/tools/zc0.c | 103 +++++++++++++++++++++++++++++++++++++++++-------- src/tools/zcc | 4 +- 10 files changed, 230 insertions(+), 35 deletions(-) create mode 100755 src/tools/embed.sh (limited to 'src/tools') diff --git a/src/tools/automap.sh b/src/tools/automap.sh index 8cefe6a..69849ca 100755 --- a/src/tools/automap.sh +++ b/src/tools/automap.sh @@ -13,10 +13,16 @@ # zc0 reads the map at build time, so it is built again for each layout. # GENMAP can name a genmap that is already built. set -e +case "${1:-}" in + --help|-h) printf '%s\n' 'usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE...' 'builds a genome and memory map sized to the supplied source files.' 'output: OUTDIR/genome.h and OUTDIR/memmap.h' 'environment: CC selects the c compiler; DOCS=1 retains documentation; GENMAP selects genmap.'; exit 0 ;; + -*) echo "automap: unknown option $1; use --help for usage" >&2; exit 2 ;; +esac cd "$(dirname "$0")/../.." +[[ $# -ge 2 ]] || { echo 'usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE...' >&2; exit 2; } P=$1; O=$2; shift 2 [ -n "$P" ] && [ -n "$O" ] || { echo "usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE..." >&2; exit 2; } CC=${CC:-gcc} +bash src/tools/embed.sh all --check || bash src/tools/embed.sh all mkdir -p "$O" ZC0=$O/.zc0 if [ -z "$GENMAP" ]; then @@ -29,14 +35,14 @@ lay() { # $1 = genome slot, or empty for the preset's own $CC -O1 -w -fwrapv src/tools/zc0.c -I"$O/map" -Isrc/core -o "$ZC0" } build() { - if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$ZC0" + if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" src/core/core.zc "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" src/core/core.zc "$@"; fi | "$ZC0" } # with DOCS=1 the genome is made again with zc0 built for docs (ZC0DOCS), so the # image carries the doc comments of the sources and the program can answer # docofs(). the image is the same code either way; the docs are stored after it. docs() { $CC -O1 -w -fwrapv -DZC0DOCS src/tools/zc0.c -I"$O/map" -Isrc/core -o "$O/.zc0docs" - if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$O/.zc0docs" + if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" src/core/core.zc "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" src/core/core.zc "$@"; fi | "$O/.zc0docs" } len() { grep -m1 '#define GLEN' "$1" | awk '{print $3}'; } lay "" diff --git a/src/tools/build.zc b/src/tools/build.zc index 547b70f..b0e3da0 100644 --- a/src/tools/build.zc +++ b/src/tools/build.zc @@ -1,13 +1,18 @@ +namespace; +include rt, run; + // compiles the rest of stdin (after the backtick) as a genome image at GORG // and writes it to stdout as little-endian 64-bit words. + + docs on; // writes the compiled genome image to stdout as little-endian 64-bit words. int main() { - int n = build_image_grow(GORG, 4096), i = 0, w; + int n = run.build_image_grow(rt.GORG, 4096), i = 0, w; while (i < n) { - w = image_words[i]; - put_c(w); put_c(w >> 8); put_c(w >> 16); put_c(w >> 24); - put_c(w >> 32); put_c(w >> 40); put_c(w >> 48); put_c(w >> 56); + w = run.image_words[i]; + rt.put_c(w); rt.put_c(w >> 8); rt.put_c(w >> 16); rt.put_c(w >> 24); + rt.put_c(w >> 32); rt.put_c(w >> 40); rt.put_c(w >> 48); rt.put_c(w >> 56); i++; } return 0; diff --git a/src/tools/embed.sh b/src/tools/embed.sh new file mode 100755 index 0000000..59a377f --- /dev/null +++ b/src/tools/embed.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# embeds source text in the compiler, so the editor can show a built-in's definition and `include std;` needs +# no file. a text becomes a function that returns it in pieces of at most 3000 bytes (the bootstrap +# compiler's token buffer is 4 KB), one byte string per piece: +# bstr NAME_part(int i) { if (i == 0) return b"..."; if (i == 1) return b"..."; ... return 0; } +# +# src/tools/embed.sh all embeds src/lib modules in the compiler +# src/tools/embed.sh all --check exits 1 when an embedded module is out of date +# src/tools/embed.sh lsp OUT writes OUT: rt_part, compiler_part and server_part, for the language server's image +set -e +case "${1:-}" in + --help|-h) printf '%s\n' 'usage: src/tools/embed.sh std|icon|all [--check]' ' src/tools/embed.sh lsp OUT' 'embeds library sources in the compiler, or writes the lsp source providers to OUT.' 'options:' ' --check report stale embedded library sources without changing files' ' --help, -h show this help and exit'; exit 0 ;; + -*) echo "embed: unknown option $1; use --help for usage" >&2; exit 2 ;; +esac +cd "$(dirname "$0")/../.." +export LC_ALL=C +# parts NAME FILE: the NAME_part function for FILE +parts() { + echo "bstr $1_part(int i) {" + awk -v LIMIT=3000 ' + function flush() { if (cnt > 0) { printf("\tif (i == %d) return b\"%s\";\n", n, buf); n++; buf = ""; cnt = 0 } } + { + line = $0; sub(/\r$/, "", line); line = line "\n" + len = length(line) + for (j = 1; j <= len; j++) { + if (cnt >= LIMIT) flush() + c = substr(line, j, 1) + if (c == "\\") e = "\\\\"; else if (c == "\"") e = "\\\""; else if (c == "\t") e = "\\t"; else if (c == "\n") e = "\\n"; else e = c + buf = buf e; cnt++ + } + } + END { flush() }' "$2" + echo " return 0;" + echo "}" +} +embed() { + name=$1; file=$2; check=$3; C=src/compiler/compiler.zc; T=$(mktemp -d) + BEGIN_MARK="// ---- embedded $name library (generated by src/tools/embed.sh from $file; do not edit) ----" + END_MARK="// ---- end embedded $name library ----" + if grep -qF "$BEGIN_MARK" "$C"; then + awk -v b="$BEGIN_MARK" '$0 == b { exit } { print }' "$C" > "$T/head" + awk -v e="$END_MARK" 'found { print } $0 == e { found = 1; next }' "$C" > "$T/tail" + else + cp "$C" "$T/head" + : > "$T/tail" + fi + { + cat "$T/head" + echo "$BEGIN_MARK" + parts "$name" "$file" + echo "$END_MARK" + cat "$T/tail" + } > "$T/new" + if [ "$check" = "--check" ]; then + cmp -s "$T/new" "$C" && result=0 || result=1 + rm -rf "$T" + return "$result" + fi + cp "$T/new" "$C" + rm -rf "$T" +} +case "$1" in +std) + embed std src/lib/std.zc "$2" + ;; +icon) + embed icon src/lib/icon.zc "$2" + ;; +all) + if [ "$2" = "--check" ]; then + embed std src/lib/std.zc --check && embed icon src/lib/icon.zc --check + else + embed std src/lib/std.zc + embed icon src/lib/icon.zc + fi + ;; +lsp) + [ -n "$2" ] || { echo "usage: src/tools/embed.sh lsp OUT" >&2; exit 2; } + { + echo "// the sources of this image, embedded (generated by src/tools/embed.sh; do not edit)" + parts rt src/runtime/rt.zc + parts compiler src/compiler/compiler.zc + parts server src/lsp/server.zc + parts core src/core/core.zc + } > "$2" + ;; +*) + echo "usage: src/tools/embed.sh all [--check] | std | icon | lsp OUT" >&2; exit 2;; +esac diff --git a/src/tools/genmap.c b/src/tools/genmap.c index dc1971c..8a258e1 100644 --- a/src/tools/genmap.c +++ b/src/tools/genmap.c @@ -10,7 +10,7 @@ ./genmap --preset run -o build/run ./genmap --preset default --check - ./genmap --mode compiler --heap-lo 8192 --heap 20480 --types 8192 --genome 245760 --frame 2048 --return 512 --mark 512 --data 3072 --snap-cap 1000 --quote-cap 300 -o build/small + ./genmap --mode compiler --heap-lo 8192 --heap 20480 --types 8192 --genome 393216 --frame 2048 --return 512 --mark 512 --data 3072 --snap-cap 1000 --quote-cap 300 -o build/small ./genmap --mode run --words 20480 -o build/run --preset default puts the code area, the compiler tables, the genome and @@ -194,7 +194,7 @@ static void preset_small(Spec *s) s->compiler = 1; s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000; s->heap_lo = 8192; s->heap = 20480; s->types = 8192; - s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; + s->genome = 393216; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; tables_tight(s); s->snap_cap = 1000; s->quote_cap = 300; s->snap = 1000; s->qbuf = 900; s->type_formula = 8192; } @@ -215,7 +215,7 @@ static void preset_default(Spec *s) s->compiler = 1; s->heap_top = 1; s->code_end = 0x80000; s->types = 65536; s->heap = 0x60000; s->total = 0x8000000; - s->genome = 0x50000; s->frame = 0x40000; s->ret = 0x10000; s->mark = 0x8000; + s->genome = 0x60000; s->frame = 0x40000; s->ret = 0x10000; s->mark = 0x8000; s->data = 0x7FF0; tables_big(s); } @@ -250,7 +250,7 @@ static void mode_defaults(Spec *s, int compiler) if (compiler) { s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000; s->heap_lo = 8192; s->heap = 20480; s->types = 8192; s->type_formula = 8192; - s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; + s->genome = 393216; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; tables_tight(s); } else { s->heap_top = 1; s->code_end = 512; s->total = 0x8000000; @@ -682,11 +682,12 @@ static void write_sources(const char *dir, const Spec *s, const Map *m) static void usage_status(int status) { - fprintf(stderr, + fprintf(status ? stderr : stdout, "usage: genmap --preset small|run|default|fixed|lsp [-o DIR] [--check]\n" " genmap --mode compiler|run [-o DIR] [--words N] [options]\n" " genmap small|run DIR\n" "options:\n" + " --help, -h show this help and exit\n" " --code N --total N --heap N --heap-lo N --types N --zfp-pad N --genome N\n" " --frame N --return N --mark N --data N\n" " --hash N --pool N --symbols N --locals N --fwd N\n" @@ -714,7 +715,7 @@ static int take(int argc, char **argv, int *i) int main(int argc, char **argv) { Spec s; Map m; - if (argc == 2 && !strcmp(argv[1], "--help")) usage_status(0); + if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) usage_status(0); const char *outdir = 0, *preset = 0, *mode = 0; int check = 0, grow = 0, i, have = 0; int total = 0, set_total = 0, set_words = 0, set_heap = 0, set_heap_lo = 0, set_types = 0, set_zfp = 0; diff --git a/src/tools/mkgenome.c b/src/tools/mkgenome.c index bb2e787..3bf1533 100644 --- a/src/tools/mkgenome.c +++ b/src/tools/mkgenome.c @@ -2,13 +2,22 @@ src/tools/build.zc) into genome.h */ #include #include +#include #ifdef _WIN32 #include #include #endif #include "memmap.h" -int main(void) +int main(int argc, char **argv) { + if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) { + fputs("usage: mkgenome [--help|-h] < image.bin > genome.h\n" + "converts little-endian 64-bit image words into a c genome header.\n" + "the image must match the memory layout selected when mkgenome is built.\n" + "options:\n --help, -h show this help and exit\n", stdout); + return 0; + } + if (argc != 1) { fputs("mkgenome: unexpected arguments; use --help for usage\n", stderr); return 2; } #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); diff --git a/src/tools/mklsp.sh b/src/tools/mklsp.sh index 5edf14a..9bab550 100755 --- a/src/tools/mklsp.sh +++ b/src/tools/mklsp.sh @@ -4,11 +4,19 @@ # src/lsp/server.zc. its memory map is sized to that program by src/tools/automap.sh, # so the genome slot is as big as it needs to be and no bigger. set -e +case "${1:-}" in + --help|-h) printf '%s\n' 'usage: src/tools/mklsp.sh [OUTDIR]' 'builds the native language server in OUTDIR/zclsp; defaults to build/lsp.' 'environment: CC selects the c compiler.'; exit 0 ;; + -*) echo "mklsp: unknown option $1; use --help for usage" >&2; exit 2 ;; +esac cd "$(dirname "$0")/../.." O=${1:-build/lsp}; CC=${CC:-gcc} # DOCS=1: the image carries the doc comments, so hover can show what a name is for -DOCS=1 bash src/tools/automap.sh lsp "$O" src/lsp/server.zc >/dev/null +mkdir -p "$O" +bash src/tools/embed.sh all --check || bash src/tools/embed.sh all +# the image carries its own sources (for go to definition into a built-in) +bash src/tools/embed.sh lsp "$O/embed.zc" +DOCS=1 bash src/tools/automap.sh lsp "$O" src/lsp/server.zc "$O/embed.zc" >/dev/null rm -rf "$O/src"; mkdir -p "$O/src" cp src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$O/memmap.h" "$O/src/" cp "$O/genome.h" "$O/src/lsp_genome.h" -$CC -O2 -w -fwrapv -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME "$O/src/core.c" "$O/src/io_linux.c" -I"$O/src" -o "$O/zclsp" +$CC -O2 -w -fwrapv -pthread -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME "$O/src/core.c" "$O/src/io_linux.c" -I"$O/src" -o "$O/zclsp" diff --git a/src/tools/mklspwasm.sh b/src/tools/mklspwasm.sh index 56ba43b..86a902e 100755 --- a/src/tools/mklspwasm.sh +++ b/src/tools/mklspwasm.sh @@ -6,6 +6,10 @@ # needs clang with the wasm32 target and wasm-ld; run src/tools/mklsp.sh first. # src/tools/mklspwasm.sh [outfile.wasm] set -e +case "${1:-}" in + --help|-h) printf '%s\n' 'usage: src/tools/mklspwasm.sh [OUTFILE.wasm]' 'builds the wasm language server; defaults to editors/vscode/server/lsp.wasm.' 'requires clang with wasm32 support and wasm-ld.' 'environment: LSP_BUILD_DIR selects the native lsp build; LSP_REUSE_BUILD=1 reuses its image.'; exit 0 ;; + -*) echo "mklspwasm: unknown option $1; use --help for usage" >&2; exit 2 ;; +esac cd "$(dirname "$0")/../.." OUT=${1:-editors/vscode/server/lsp.wasm} LSP_BUILD_DIR=${LSP_BUILD_DIR:-build/lsp} @@ -14,7 +18,7 @@ if [ "${LSP_REUSE_BUILD:-0}" != 1 ]; then bash src/tools/mklsp.sh "$LSP_BUILD_DI clang --target=wasm32 -O2 -w -fwrapv -nostdlib -fno-builtin -mbulk-memory -DJTYPE=int -DUSE_LSP_GENOME \ -I"$LSP_BUILD_DIR/src" -Isrc/micro src/micro/micro.c src/micro/port_wasm_stream.c \ -Wl,--no-entry -Wl,--export=wasm_chunk -Wl,--export=wasm_chunk_cap -Wl,--export=wasm_feed \ - -Wl,--export=wasm_close -Wl,--export=wasm_start -Wl,--export=wasm_resume \ + -Wl,--export=wasm_polling -Wl,--export=wasm_close -Wl,--export=wasm_start -Wl,--export=wasm_resume \ -Wl,--export=memory -Wl,--allow-undefined -Wl,--initial-memory=41943040 -Wl,-z,stack-size=65536 \ -o "$OUT" ls -l "$OUT" diff --git a/src/tools/mkvsix.sh b/src/tools/mkvsix.sh index 5ca8ef5..39fb9c4 100755 --- a/src/tools/mkvsix.sh +++ b/src/tools/mkvsix.sh @@ -6,6 +6,10 @@ # server build (src/tools/mklsp.sh) runs first if build/lsp is missing. # src/tools/mkvsix.sh [outfile.vsix] set -e +case "${1:-}" in + --help|-h) printf '%s\n' 'usage: src/tools/mkvsix.sh [OUTFILE.vsix]' 'builds and packages the vscode extension; defaults to build/zc-language-support.vsix.' 'requires node, tsc and vsce; includes the wasm server when clang supports wasm32.' 'environment: VSCE selects the extension packager.'; exit 0 ;; + -*) echo "mkvsix: unknown option $1; use --help for usage" >&2; exit 2 ;; +esac cd "$(dirname "$0")/../.." OUT=${1:-$PWD/build/zc-language-support.vsix} case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 5ef5737..446557b 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -27,6 +27,7 @@ enum { O_Q, O_LD, O_ST, O_PICK, O_DROP, O_SWAP, O_CALL, O_JMP, O_JZ, O_RET, O_IN, O_EMPTY, O_OUT, O_ADD, O_SUB, O_MUL, O_AND, O_OR, O_XOR, O_SHR, O_LT }; static const char *mn = "q@!$_~cjzr,?.+-*&|^><%"; +static char module_name[64]; static char *src; static int pos, line = 1, tstart, hdr_start; /* the signature of the function being defined: its header's source text, for the language server (ZC0DOCS) */ static char sgtext[400], sgname[80]; @@ -78,6 +79,7 @@ static void doccomment(void) /* pos is on the first slash */ while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--; bol = k < 0 || src[k] == '\n'; pos += 2; + if (src[pos] == ':') { dn = 0; dlast = cl; docpending = 0; while (src[pos] && src[pos] != '\n') pos++; return; } if (!bol) { while (src[pos] && src[pos] != '\n') pos++; return; } if (dn && dlast != cl - 1) dn = 0; if (src[pos] == ' ') pos++; @@ -176,6 +178,11 @@ static void next(void) while (src[pos] && src[pos] != '"' && i < 4095) ts[i++] = (char)esc(); ts[i] = 0; if (src[pos] == '"') pos++; tk = T_BSTR; return; } + if (src[pos] == '.' && (!strcmp(ts,"rt") || !strcmp(ts,"run") || !strcmp(ts,"core") || !strcmp(ts,"lsp") || !strcmp(ts,"std") || !strcmp(ts,"icon"))) { + ts[i++] = src[pos++]; + while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; + ts[i] = 0; + } tk = T_ID; return; } if (src[pos] == '\'') { pos++; tv = esc(); if (src[pos++] != '\'') die("bad char literal"); tk = T_NUM; strcpy(ts, "'c'"); return; } @@ -198,10 +205,24 @@ typedef struct { char name[64]; int kind, sig, defd; int64_t val; } Sym; static Sym sy[16384]; static int nsy; typedef struct { int at, sym; } Fix; static Fix fx[65536]; static int nfx; -static Sym *find(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; } +static Sym *find_raw(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; } +static void qualify(char *dst, size_t size, const char *name) { + if (!module_name[0] || strchr(name, '.')) snprintf(dst, size, "%s", name); + else if (!strncmp(name, "doc ", 4) || !strncmp(name, "sig ", 4)) snprintf(dst, size, "%.4s%s.%s", name, module_name, name + 4); + else snprintf(dst, size, "%s.%s", module_name, name); +} +static Sym *find(const char *name) { + char full[256]; Sym *s; + qualify(full, sizeof full, name); s = find_raw(full); if (s) return s; + s = find_raw(name); if (s || strchr(name, '.')) return s; + const char *modules[] = { "rt", "run", "core", "lsp", 0 }; + for (int i = 0; modules[i]; i++) { snprintf(full,sizeof full,"%s.%s",modules[i],name); s=find_raw(full); if(s) return s; } + return 0; +} static Sym *add(const char *n, int kind, int64_t val) { - Sym *s = find(n); + char full[256]; qualify(full, sizeof full, n); n = full; + Sym *s = find_raw(n); if (s && s->kind == K_FN && kind == K_FN && !s->defd) { s->val = val; return s; } /* prototype or forward use */ s = &sy[nsy++]; strncpy(s->name, n, 63); s->kind = kind; s->val = val; s->defd = 1; s->sig = 0; return s; } @@ -318,11 +339,11 @@ static int64_t cexpr(void) /* ---- types ---- zc0 has no types: a type is read and thrown away. all it has to know is the - shape, so that [n]T still reserves n words and the rest reserve one. the + shape, so that T[n] still reserves n words and the rest reserve one. the grammar is the compiler's own (see ptype in compiler.zc): ptype a type, then any number of (params): int(int, int) is a function type ptype0 a type with its stars: int*, vec*, point** - ptype1 a base: [n]T, []T, fixed []T, fn, a name, or a generic instance + ptype1 a base: T[n], T[?], fixed T[?], fn, a name, or a generic instance each returns the words a variable of the type takes: n for a fixed array, 1 for a growable handle or anything scalar. */ static int ptype0(void); @@ -330,12 +351,7 @@ static int ptype(void); static int ptype1(void) { int n, e; - if (accept("[")) { - if (accept("]")) { ptype0(); return 1; } /* []T is a handle */ - n = (int)cexpr(); expect("]"); - e = ptype0(); - return n > 0 ? n * e : e; - } + if (is("[")) { die("array types are postfix: T[N], T[] or T[?]"); return 1; } if (is("fixed")) { next(); return ptype0(); } if (is("fn")) { next(); return 1; } /* fn(int): the parameters follow, in ptype */ if (tk != T_ID) { die("a type name"); return 1; } @@ -350,14 +366,30 @@ static int ptype1(void) static int ptype0(void) { int n = ptype1(); - while (accept("*")) n = 1; + while (is("*") || is("[")) { + if (accept("*")) n = 1; + else { + int count = 0, cap = 8, *dims = malloc(cap * sizeof(int)); + if (!dims) die("cannot allocate array dimensions"); + while (accept("[")) { + int length; + if (accept("?")) { expect("]"); length = -2; } + else if (accept("]")) length = -1; + else { length = (int)cexpr(); expect("]"); } + if (count == cap) { cap *= 2; int *more = realloc(dims, cap * sizeof(int)); if (!more) die("cannot grow array dimensions"); dims = more; } + dims[count++] = length; + } + while (count) { int length = dims[--count]; if (length == -2) n = 1; else n = length < 0 ? 0 : n * length; } + free(dims); + } + } return n; } static int ptype(void) { int n = ptype0(); while (accept("(")) { - if (!accept(")")) { do { ptype(); } while (accept(",")); expect(")"); } + if (!accept(")")) { do { ptype(); if (accept(".")) ident(); } while (accept(",")); expect(")"); } n = 1; } return n; @@ -435,6 +467,7 @@ static void bstrlit(void) } static int returns_bstr(const char *n) { + if (!strncmp(n, "rt.", 3)) n += 3; return !strcmp(n, "cat_s") || !strcmp(n, "slice_s") || !strcmp(n, "n_to_s") || !strcmp(n, "new_s"); } static int args(void) { int n = 0; expect("("); if (!accept(")")) { do { expr(); rval(); n++; } while (accept(",")); expect(")"); } return n; } @@ -457,6 +490,13 @@ static void primary(void) if (s && s->kind == K_GARR) { lit(s->val); return; } if (is("(")) { int na = args(); + if (s && s->kind == K_ENH) { + if (na != (s->sig & 15)) die("wrong number of enhancement inputs"); + op(22 + (int)s->val); + if (((s->sig >> 4) & 15) == 0) lit(0); + else if (((s->sig >> 4) & 15) != 1) die("multiple enhancement outputs need asm"); + xs = 0; xf = 0; return; + } if (!strcmp(n, "put_s") && na == 1 && xs) fnref("put_bs"); else if (!strcmp(n, "len")) fnref("len_s"); else fnref(n); @@ -671,7 +711,7 @@ static void fndef(int ret) char *n = ident(); Sym *s; int np = 0, tags = 0, t, frame, i, helper, busy, done, check, node; if (dsn && !dnm[0]) strcpy(dnm, n); char pn[16][64]; - s = find(n); + char full_name[256]; qualify(full_name, sizeof full_name, n); s = find_raw(full_name); if (!s || s->kind != K_FN) { s = add(n, K_FN, 0); s->defd = 0; } expect("("); if (!accept(")")) { @@ -705,7 +745,7 @@ static void fndef(int ret) nlo = 0; nslot = 0; nepi = 0; infn = 1; for (i = 0; i < np; i++) { strcpy(lo[nlo].name, pn[i]); lo[nlo].arr = 0; lo[nlo].flt = 0; lo[nlo].str = 0; lo[nlo++].slot = nslot++; } /* retain the caller's pointer until the frame has its final segment. */ - helper = !find("frame_busy") || !strcmp(s->name, "frame_grow") || !strcmp(s->name, "frame_leave"); + helper = !find("frame_busy") || !strcmp(s->name, "rt.frame_grow") || !strcmp(s->name, "rt.frame_leave"); lit(ZFP); op(O_LD); if (!helper) { lit(0); op(O_PICK); } frame = here; lit(0); op(O_SUB); lit(ZFP); op(O_ST); if (!helper) { @@ -756,6 +796,12 @@ static void enhdef(void) } static void top1(void) { + if (accept("namespace")) { + if (is(";")) module_name[0] = 0; + else { snprintf(module_name, sizeof module_name, "%s", ident()); } + expect(";"); return; + } + if (accept("include")) { do { ident(); } while (accept(",")); expect(";"); return; } { if (accept("unchecked")) { top1(); return; } if (accept("fn")) { fndef(0); return; } @@ -790,6 +836,21 @@ static void top1(void) } } +/* section headings organize the source; they do not document a symbol. */ +static int doc_section(const char *text, int length) +{ + int start = 0, end, a, b; + while (start < length) { + end = start; while (end < length && text[end] != '\n') end++; + a = start; b = end; + while (a < b && isspace((unsigned char)text[a])) a++; + while (b > a && isspace((unsigned char)text[b - 1])) b--; + if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1; + start = end + 1; + } + return 0; +} + /* one item, with the doc comment above it kept under the name it declares */ static void top(void) { @@ -801,7 +862,7 @@ static void top(void) if (dn && dline == line) { memcpy(dsnap, dbuf, dn); dsn = dn; dline = 0; } top1(); #ifdef ZC0DOCS /* enabled for images that carry editor documentation */ - if (dsn && dnm[0]) { + if (dsn && dnm[0] && !doc_section(dsnap, dsn)) { int j = here, a, i = 0, k, c; uint64_t w; char nm[80]; lit(0); op(O_JMP); a = here; emit(dsn); @@ -834,8 +895,16 @@ static void top(void) } } -int main(void) +int main(int argc, char **argv) { + if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) { + fputs("usage: zc0 [--help|-h] < source.zc > genome.h\n" + "compiles bootstrap source from stdin into a c genome header on stdout.\n" + "source must define start(); memory layout is selected when zc0 is built.\n" + "options:\n --help, -h show this help and exit\n", stdout); + return 0; + } + if (argc != 1) { fputs("zc0: unexpected arguments; use --help for usage\n", stderr); return 2; } #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); @@ -856,7 +925,7 @@ int main(void) } buf[n] = 0; src = buf; next(); /* image header: set the frame pointer, jump to start, then the symbol blob pointer and image end */ - lit(FTOP); lit(ZFP); op(O_ST); fnref("start"); op(O_JMP); emit(0); emit(0); + lit(FTOP); lit(ZFP); op(O_ST); fnref(strstr(src, "namespace run;") ? "run.start" : "start"); op(O_JMP); emit(0); emit(0); top(); for (i = 0; i < nfx; i++) { Sym *s = &sy[fx[i].sym]; diff --git a/src/tools/zcc b/src/tools/zcc index 19904ea..3fb8f0c 100755 --- a/src/tools/zcc +++ b/src/tools/zcc @@ -22,7 +22,7 @@ while [ $# -gt 0 ]; do case "$1" in -o) [ $# -ge 2 ] || { echo "zcc: -o needs a name" >&2; exit 2; }; out="$2"; shift 2 ;; -k) keep=1; shift ;; - -h|--help) sed -n '2,15p' "$0"; exit 0 ;; + -h|--help) printf '%s\n' 'usage: src/tools/zcc [-o OUT] [-k] FILE.zc' 'compiles a source file into a standalone binary; links statically where supported.' 'options:' ' -o OUT output binary path; defaults to the source basename without .zc' ' -k keep the temporary build directory' ' --help, -h show this help and exit' 'environment: CC selects the c compiler; CFLAGS adds compiler flags.'; exit 0 ;; -*) echo "zcc: unknown option $1" >&2; exit 2 ;; *) break ;; esac @@ -49,6 +49,6 @@ if ! printf 'int main(void){return 0;}\n' | $CC -x c -static -o "$work/probe" - static="" echo "zcc: this toolchain cannot link -static; the binary is dynamic" >&2 fi -$CC -O2 -w -fwrapv ${CFLAGS:-} $static "$work/run/micro.c" "$work/run/port_linux.c" \ +$CC -O2 -w -fwrapv -pthread -DZY_THREADS ${CFLAGS:-} $static "$work/run/micro.c" "$work/run/port_linux.c" \ -I"$work/run" -o "$out" echo "zcc: wrote $out ($(wc -c < "$out" | tr -d ' ') bytes)" -- cgit v1.2.3