diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d6b105..3c3f21e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,18 @@ jobs: - name: Run unit tests run: cargo test --release + - name: Native backend gate + run: bash tests/run_native_gate.sh + - name: Full test suite (run_tests.sh) run: ./run_tests.sh - name: Build the self-hosted compiler (epc) run: ./target/release/ernos epc.ep + - name: Check-only safety parity + run: bash tests/run_check_gate.sh + - name: Self-hosted parity + rejection suite run: bash tests/run_epc_parity.sh @@ -58,7 +64,7 @@ jobs: run: | for f in conformance/test_*.ep; do echo "Compiling $f..." - cargo run --release -- "$f" || continue + cargo run --release -- "$f" base=$(basename "$f" .ep) expected="conformance/${base}.expected" if [ -f "$expected" ]; then @@ -93,12 +99,18 @@ jobs: - name: Run unit tests run: cargo test --release + - name: Native backend gate + run: bash tests/run_native_gate.sh + - name: Full test suite (run_tests.sh) run: ./run_tests.sh - name: Build the self-hosted compiler (epc) run: ./target/release/ernos epc.ep + - name: Check-only safety parity + run: bash tests/run_check_gate.sh + - name: Self-hosted parity + rejection suite run: bash tests/run_epc_parity.sh @@ -130,7 +142,7 @@ jobs: run: | for f in conformance/test_*.ep; do echo "Compiling $f..." - cargo run --release -- "$f" || continue + cargo run --release -- "$f" base=$(basename "$f" .ep) expected="conformance/${base}.expected" if [ -f "$expected" ]; then diff --git a/AGENT.md b/AGENT.md index 32a1bbb..572079c 100644 --- a/AGENT.md +++ b/AGENT.md @@ -60,13 +60,13 @@ cargo run -- epc.ep && ./epc tests/test_basic_math.ep && ./test_basic_math # Stronger gates (run after any epc-visible change): bash tests/run_fixpoint.sh # 3-stage byte-identical self-compile bash tests/run_epc_parity.sh # self-hosted coverage scoreboard (must not regress) -bash tests/run_differential.sh # both compilers must AGREE on 39 adversarial programs +bash tests/run_differential.sh # both compilers must AGREE on 43 adversarial programs bash bootstrap/verify.sh # clang-only, Rust-free 3-stage fixpoint + parity + freshness ``` -Current state of these gates: `run_tests.sh` **72/72**, `run_epc_parity.sh` **54/54 runnable + 12/12 compile-error rejections** (0 wrongly accepted), `run_differential.sh` **39/39 agree**, `run_fixpoint.sh` byte-identical, `bootstrap/verify.sh` green. +Current state of these gates: `run_tests.sh` **84/84**, `run_epc_parity.sh` **55/55 runnable + 12/12 compile-error rejections** (0 wrongly accepted), `run_differential.sh` **43/43 agree**, `run_fixpoint.sh` byte-identical, `bootstrap/verify.sh` green. -The self-hosted compiler is ~6,400 lines of real ErnosPlain — `ep_lexer.ep` (lexer), `ep_parser.ep` (parser), `ep_check.ep` (semantic checker), `ep_optimizer.ep` (constant folding + DCE), `ep_codegen.ep` (C codegen), `epc.ep` (driver) — that exercises the full type system, all builtin functions, list/string operations, struct creation, pattern matching, closures, floats, traits, `try`/Result, and coroutine async. If it doesn't compile, you broke something. +The self-hosted compiler is ~7,100 source lines of real ErnosPlain (plus the generated runtime module) — `ep_lexer.ep` (lexer), `ep_parser.ep` (parser), `ep_check.ep` (semantic checker), `ep_optimizer.ep` (constant folding + DCE), `ep_codegen.ep` (C codegen), `epc.ep` (driver) — that exercises the full type system, all builtin functions, list/string operations, struct creation, pattern matching, closures, floats, traits, `try`/Result, and coroutine async. If it doesn't compile, you broke something. **Shared runtime.** `runtime/ep_runtime.c` + `runtime/ep_builtins.c` are the single source of truth for the emitted C runtime. The Rust compiler embeds them via `include_str!`; the self-hosted compiler embeds them via the generated `ep_runtime_gen.ep`. After editing either `runtime/*.c` file, regenerate: `./target/release/ernos tools/gen_runtime_ep.ep && ./tools/gen_runtime_ep`. @@ -156,7 +156,7 @@ Every language construct should read like a sentence a non-programmer could unde Symbol shortcuts (`+`, `<`, `==`, `&&`) are allowed as opt-in shorthands for experienced programmers. The plain English form is always the primary syntax. ### Self-Hosting is Non-Negotiable -The self-hosted compiler (`epc.ep` + modules) must always compile itself using the Rust bootstrap compiler. This is the ultimate integration test. If the type checker rejects the self-hosted compiler, the type checker is too strict — not the self-hosted compiler is wrong. The self-hosted compiler is ~6,400 lines of real, working ErnosPlain. It is the language's own dogfood, and it bootstraps from a frozen C snapshot with no Rust in the loop. +The self-hosted compiler (`epc.ep` + modules) must always compile itself using the Rust bootstrap compiler. This is the ultimate integration test. If the type checker rejects the self-hosted compiler, the type checker is too strict — not the self-hosted compiler is wrong. The self-hosted compiler is ~7,100 source lines of real, working ErnosPlain plus its generated runtime module. It is the language's own dogfood, and it bootstraps from a frozen C snapshot with no Rust in the loop. ### Cross-Platform by Default Ernos must work on: @@ -201,8 +201,8 @@ Codegen (codegen.rs) → C source code (includes full runtime inline) ↓ Clang/GCC → Native binary -Alternative: --native flag - Codegen → ARM64 or x86_64 assembly → system assembler → system linker → native binary +Alternative: --native flag (supported AST subset) + Codegen → ARM64 or x86_64 assembly + shared C runtime → assembler/C compiler/linker → native binary ``` --- @@ -388,7 +388,7 @@ These are implemented as C functions in the runtime (codegen.rs). They are NOT E `channel` (keyword), `create_channel()`, `send value to channel` (statement), `receive from channel` (expression), `spawn function(args)` (statement), `channel_has_data(ch)`, `channel_try_recv(ch)`, `channel_select(ch_list)`, `send_channel(ch and val)`, `recv_channel(ch)`, `create_task_group()`, `add_task_group(group and fut)`, `wait_task_group(group)`, `async_timeout(timeout_ms and fut)`, `cancel_task(fut)` ### Networking -`ep_net_connect(host and port)`, `ep_net_listen(port)`, `ep_net_accept(server)`, `ep_net_send(socket and data)`, `ep_net_recv(socket and bufsize)`, `ep_net_recv_bytes(socket and bufsize)`, `ep_net_close(socket)`, `ep_http_request(method and url and body and headers)` +`ep_net_connect(host and port)`, `ep_net_listen(port)`, `ep_net_accept(server)`, `ep_net_send(socket and data)`, `ep_net_send_raw(socket and data_ptr and byte_count)`, `ep_net_recv(socket and bufsize)`, `ep_net_recv_bytes(socket and bufsize)`, `ep_net_close(socket)`, `ep_http_request(method and url and headers and body)` ### JSON `json_get_int(json and key)`, `json_get_string(json and key)`, `json_get_bool(json and key)` @@ -428,7 +428,7 @@ All FFI functions work with `long long` arguments. Pointers and integers are pas ```bash # Compile and run ernos program.ep # Compile to ./program, then run -ernos program.ep --native # Compile via native assembly backend +ernos program.ep --native # Native assembly frontend + C runtime # C header binding generation ernos bind header.h [-o bindings.ep] # Parse C header → ErnosPlain bindings @@ -441,7 +441,7 @@ ernos transpile file.js [-o out.ep] # JavaScript → ErnosPlain # Other ernos --version # Show version ernos --list-builtins # List all builtin functions -ernos check file.ep # Type-check without compiling +ernos check file.ep # Full static validation without codegen ``` --- @@ -460,8 +460,9 @@ cargo build --release 2>&1 | tail -3 # 3. Self-hosting gate (MANDATORY when type checker/codegen/parser changed) cargo run -- epc.ep && ./epc tests/test_basic_math.ep && ./test_basic_math -# 4. Native backend gate (when native codegen changed) -cargo run -- tests/test_basic_math.ep --native && ./tests/test_basic_math +# 4. Native and check-only safety gates +bash tests/run_native_gate.sh +bash tests/run_check_gate.sh ``` If ANY step fails, the change is not ready. Fix it before committing. Do not commit with known failures. diff --git a/LANGUAGE_REFERENCE.md b/LANGUAGE_REFERENCE.md index a6ba9e3..085785e 100644 --- a/LANGUAGE_REFERENCE.md +++ b/LANGUAGE_REFERENCE.md @@ -521,13 +521,17 @@ Supported extensions: `.py`, `.c`, `.h`, `.js`, `.mjs`, `.go`, `.rs`, `.rb`, `.j ```bash ernos program.ep --release # Compile with -O3 + LTO ernos program.ep --debug # Compile with -O0 + debug symbols -ernos check program.ep # Type check only, no binary +ernos check program.ep # Full static validation, no binary ernos format program.ep # Auto-format source code ernos --repl # Interactive REPL -ernos program.ep --native # Native assembly (no Clang) +ernos program.ep --native # Native assembly frontend + C runtime ernos program.ep --asan # AddressSanitizer ``` +The `--native` backend emits architecture-specific assembly for the supported +language subset, then compiles and links the shared C runtime. It therefore +requires the system assembler/linker and a C compiler (Clang or GCC). + ### Cross-Platform The generated C code compiles on any platform with a C compiler: ```bash diff --git a/README.md b/README.md index d5873ea..ba66c69 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@

Version License: MIT - Rust suite 72/72 - Self-hosted parity 54/54 + Rust suite 84/84 + Self-hosted parity 55/55 Rejection gate 12/12 Clang-only bootstrap Byte-identical fixpoint @@ -41,7 +41,7 @@ Ernos ships **two** complete compilers for the same language: - **`ernos`** — the reference compiler, written in Rust (~30k lines). - **`epc`** — the self-hosted compiler, **written entirely in Ernos** (`ep_lexer.ep`, `ep_parser.ep`, `ep_check.ep`, `ep_optimizer.ep`, `ep_codegen.ep`, `epc.ep`). -`epc` compiles **every one of the 54 runnable test programs**, rejects **all 12** compile-error tests through its own semantic checker, and — compiling its own source — reaches a **byte-identical fixpoint** (`gen2 == gen3`). A frozen C snapshot (`bootstrap/epc_bootstrap.c`) means the whole toolchain rebuilds from **clang alone** — no Rust, no `cargo`, no bootstrap chicken-and-egg. This is verified end-to-end on every change, **with zero disclosed caveats**. +`epc` compiles **every one of the 55 runnable primary test programs**, rejects **all 12** compile-error tests through its own semantic checker, and — compiling its own source — reaches a **byte-identical fixpoint** (`gen2 == gen3`). A frozen C snapshot (`bootstrap/epc_bootstrap.c`) means the whole toolchain rebuilds from **clang alone** — no Rust, no `cargo`, no bootstrap chicken-and-egg. This is verified end-to-end on every change, **with zero disclosed caveats**. > `clang bootstrap/epc_bootstrap.c -o epc && ./epc epc.ep` → a working compiler that recompiles itself and passes the full suite. @@ -158,19 +158,24 @@ Pre-built bindings for C libraries via `ep_dlopen`/`ep_dlsym`/`ep_dlcall`: | **Compiler** | `ernos program.ep` | Compile to native binary | | **REPL** | `ernos --repl` | Interactive evaluation with session state | | **Formatter** | `ernos format file.ep` | Auto-format source code | -| **Checker** | `ernos check file.ep` | Type/syntax validation without compiling | +| **Checker** | `ernos check file.ep` | Full static validation without code generation | | **Test Runner** | `ernos test file.ep` | Run tests | | **Builtins** | `ernos --list-builtins` | Show all built-in functions | | **Debug** | `ernos file.ep --debug` | Compile with `-O0 -g` | | **Release** | `ernos file.ep --release` | Compile with `-O3 -flto` | | **ASAN** | `ernos file.ep --asan` | Compile with AddressSanitizer | | **WASM** | `ernos file.ep --wasm` | Compile to WebAssembly | -| **Native** | `ernos file.ep --native` | Compile via native assembly (no Clang) | +| **Native** | `ernos file.ep --native` | Native assembly frontend + shared C runtime | | **LSP** | `ernos --lsp` | Language Server Protocol for editor support | | **Doc Gen** | `ernos doc file.ep -o api.md` | Generate API documentation from doc comments | | **Bind** | `ernos bind header.h` | Generate .ep bindings from C headers | | **Transpile** | `ernos transpile file.py` | Translate Python/C/JS/Go/Rust/Ruby/Java/TS to EP | +`--native` supports a deliberately smaller AST subset than the default C +backend and reports unsupported constructs explicitly. It emits the program +frontend as architecture-specific assembly, but compiles and links the shared +C runtime, so a system assembler/linker and Clang or GCC are still required. + ### 🌍 Platform Support - **macOS** (ARM64 + x86_64) — primary development platform - **Linux** (x86_64 + aarch64, GCC or Clang) — supported @@ -336,23 +341,23 @@ Source (.ep) > **Note:** The codegen phase performs additional ownership checks (use-after-move, borrow violations) as a safety net alongside the dedicated borrow checker. Both must pass for compilation to succeed. -### Reference compiler (Rust) — `~30,000` lines across 24 modules +### Reference compiler (Rust) — `~30,000` lines across 27 modules | File | Lines | Description | |------|-------|-------------| | `src/lexer.rs` | 896 | Tokenizer with indentation tracking | -| `src/parser.rs` | 1,639 | Recursive-descent parser with Pratt precedence | -| `src/type_check.rs` | 1,987 | Type inference via unification (HM-style; no let-generalization) | +| `src/parser.rs` | 1,647 | Recursive-descent parser with Pratt precedence | +| `src/type_check.rs` | 2,022 | Type inference via unification (HM-style; no let-generalization) | | `src/borrow_check.rs` | 783 | Ownership, borrowing, Send/Sync analysis | | `src/optimizer.rs` | 1,577 | Constant folding, DCE, CSE, LICM, inlining, loop unrolling | -| `src/codegen.rs` | 3,958 | C code generation (runtime lives in `runtime/`, embedded via `include_str!`) | +| `src/codegen.rs` | 4,035 | C code generation (runtime lives in `runtime/`, embedded via `include_str!`) | | `src/llvm_codegen.rs` | 76 | LLVM IR backend (via `clang -emit-llvm`) | | `src/lsp.rs` | 1,198 | Language Server Protocol implementation | | `src/diagnostics.rs` | 382 | Rich error reporting with ANSI colors | | `src/native_codegen.rs` | 656 | ARM64 native-assembly backend (macOS + Linux) | | `src/x86_64_codegen.rs` | 623 | x86-64 native-assembly backend (macOS + Linux) | | `src/bind_c.rs` | 1,441 | C-header binding generator (zero-dependency) | -| `src/main.rs` | 2,087 | CLI, imports, REPL, compilation pipeline | +| `src/main.rs` | 2,131 | CLI, imports, REPL, compilation pipeline | | `src/transpile_py.rs` | 2,673 | Python → Ernos transpiler | | `src/transpile_c.rs` | 1,376 | C → Ernos transpiler | | `src/transpile_js.rs` | 1,235 | JavaScript → Ernos transpiler | @@ -364,14 +369,17 @@ Source (.ep) | `src/emit_c.rs` | 569 | Ernos → C emitter | | `src/emit_js.rs` | 622 | Ernos → JavaScript emitter (enums → ES classes, trait-impl dispatch) | | `src/emit_python.rs` | 640 | Ernos → Python emitter | -| **Total** | **~30,094** | | +| `src/arm64.rs` | 264 | ARM64 machine-code encoder | +| `src/ast.rs` | 198 | Shared abstract syntax tree definitions | +| `src/token.rs` | 105 | Token and source-span definitions | +| **Total** | **30,258** | | ### Shared C runtime — one source of truth, embedded by both compilers | File | Lines | Description | |------|-------|-------------| -| `runtime/ep_runtime.c` | 4,732 | Generational GC (precise STW + conservative stack scan, write barrier, OOM-guarded allocators), pointer-safe object accessors, coroutine/`EpFuture` scheduler, TCP/HTTP, SQLite, crypto, FFI | -| `runtime/ep_builtins.c` | 161 | Builtin registration glue | +| `runtime/ep_runtime.c` | 4,843 | Generational GC (precise STW + conservative stack scan, write barrier, OOM-guarded allocators), pointer-safe object accessors, coroutine/`EpFuture` scheduler, TCP/HTTP, SQLite, crypto, FFI | +| `runtime/ep_builtins.c` | 234 | Builtin registration glue | The reference compiler embeds this via `include_str!`; the self-hosted compiler embeds the **byte-for-byte same source** through the generated `ep_runtime_gen.ep` (regenerate with `tools/gen_runtime_ep.ep`). Both compilers therefore emit the identical GC, accessors, and allocators — there is no "runtime drift" between them. @@ -381,17 +389,17 @@ The reference compiler embeds this via `include_str!`; the self-hosted compiler This is the part most languages never finish. Ernos does — and proves it on every commit. -### The self-hosted compiler (`epc`) — written in Ernos, ~6,400 lines +### The self-hosted compiler (`epc`) — written in Ernos, ~7,100 source lines plus generated runtime | File | Lines | Description | |------|-------|-------------| | `ep_lexer.ep` | 817 | Lexer — indentation, f-string desugaring, English keyword aliases | -| `ep_parser.ep` | 1,449 | Parser — full grammar, `import "x" as alias`, traits, enums, closures | -| `ep_check.ep` | 301 | Semantic checker — reserved-name shadowing, Send-safety, list homogeneity, **enum-variant field-type checking** | +| `ep_parser.ep` | 1,500 | Parser — full grammar, `import "x" as alias`, traits, enums, closures | +| `ep_check.ep` | 405 | Semantic checker — reserved-name shadowing, Send-safety, list homogeneity, **enum-variant field-type checking** | | `ep_optimizer.ep` | 122 | Constant folding + dead-code elimination | -| `ep_codegen.ep` | 3,342 | C code generator — closures, floats, traits, iterator protocol, `try`/Result, coroutine async, globals | -| `epc.ep` | 370 | Compiler driver — module flattening, aliased imports, `check`/`format`/`repl`/`doc` subcommands | -| `ep_runtime_gen.ep` | 5,070 | Generated: emits the shared C runtime verbatim | +| `ep_codegen.ep` | 3,864 | C code generator — closures, floats, traits, iterator protocol, `try`/Result, coroutine async, globals | +| `epc.ep` | 377 | Compiler driver — module flattening, aliased imports, `check`/`format`/`repl`/`doc` subcommands | +| `ep_runtime_gen.ep` | 5,259 | Generated: emits the shared C runtime verbatim | The self-hosted pipeline is a full `lex → parse → **check** → **optimize** → codegen`, not just a lex/parse/emit skeleton. @@ -399,10 +407,10 @@ The self-hosted pipeline is a full `lex → parse → **check** → **optimize** | Gate | Result | Verified by | |------|--------|-------------| -| Reference-compiler suite | **72 / 72** | `./run_tests.sh` | -| Self-hosted parity (runnable programs `epc` compiles + runs correctly) | **54 / 54** | `bash tests/run_epc_parity.sh` | +| Reference-compiler suite | **84 / 84** | `./run_tests.sh` | +| Self-hosted parity (runnable programs `epc` compiles + runs correctly) | **55 / 55** | `bash tests/run_epc_parity.sh` | | Compile-error gate (programs `epc`'s checker must reject) | **12 / 12**, 0 wrongly accepted | `bash tests/run_epc_parity.sh` | -| Differential suite (both compilers agree on 39 adversarial programs) | **39 / 39** | `bash tests/run_differential.sh` | +| Differential suite (both compilers agree on 43 adversarial programs) | **43 / 43** | `bash tests/run_differential.sh` | | 3-stage self-compilation fixpoint (`gen2 == gen3`, byte-identical) | **OK** | `bash tests/run_fixpoint.sh` | | Rust-free, clang-only bootstrap → recompile → fixpoint → full suite | **OK** | `bash bootstrap/verify.sh` | | Cargo build warnings | **0** | `cargo build --release` | @@ -432,14 +440,16 @@ The Rust compiler builds the same self-hosted compiler and remains the home of t ## The Test Matrix ```bash -./run_tests.sh # reference (Rust) compiler: 72/72 -bash tests/run_epc_parity.sh # self-hosted: 54/54 runnable + 12/12 rejections -bash tests/run_differential.sh # both compilers agree on 39 adversarial programs +./run_tests.sh # reference (Rust) compiler: 84/84 +bash tests/run_epc_parity.sh # self-hosted: 55/55 runnable + 12/12 rejections +bash tests/run_differential.sh # both compilers agree on 43 adversarial programs +bash tests/run_native_gate.sh # native backend smoke + safety rejection +bash tests/run_check_gate.sh # check-only ownership parity bash tests/run_fixpoint.sh # 3-stage byte-identical fixpoint bash bootstrap/verify.sh # clang-only, Rust-free end-to-end proof ``` -Every one of the 66 programs in `tests/` (54 runnable + 12 compile-error) is exercised by **both** compilers, and `tests/differential/` holds 39 adversarial programs (operator precedence, GC stress, closure corner cases, type-safety probes) on which the two compilers' compiled binaries must produce byte-identical output. Conformance tests live in [`conformance/`](conformance/); the formal grammar and type/memory/concurrency rules are in [`spec/ernos-spec.md`](spec/ernos-spec.md). +Every one of the 67 primary programs in `tests/` (55 runnable + 12 compile-error) is exercised by **both** compilers. The reference suite also runs 6 conformance and 11 forensic programs for 84 total cases. `tests/differential/` holds 43 adversarial programs (operator precedence, GC stress, closure corner cases, type-safety probes) on which the two compilers' compiled binaries must produce byte-identical output. Conformance tests live in [`conformance/`](conformance/); the formal grammar and type/memory/concurrency rules are in [`spec/ernos-spec.md`](spec/ernos-spec.md). --- diff --git a/bootstrap/epc_bootstrap.c b/bootstrap/epc_bootstrap.c index a84fa16..499cf08 100644 --- a/bootstrap/epc_bootstrap.c +++ b/bootstrap/epc_bootstrap.c @@ -37,6 +37,7 @@ typedef int pthread_attr_t; #define pthread_detach(t) ((void)(t), 0) #else #include +#include #endif #include #include @@ -1796,6 +1797,7 @@ long long json_get_int(long long json_val, long long key_val); long long json_get_bool(long long json_val, long long key_val); long long ep_sha1(long long data_val); long long ep_net_recv_bytes(long long fd, long long count); +long long ep_net_send_raw(long long fd, long long data_ptr, long long count); long long channel_try_recv(long long chan_ptr, long long out_ptr); long long channel_has_data(long long chan_ptr); long long channel_select(long long channels_list, long long timeout_ms); @@ -2068,6 +2070,11 @@ long long ep_net_send(long long fd, const char* data) { return 0; } +long long ep_net_send_raw(long long fd, long long data_ptr, long long count) { + (void)fd; (void)data_ptr; (void)count; + return 0; +} + char* ep_net_recv(long long fd, long long max_len) { (void)fd; (void)max_len; char* empty = malloc(1); @@ -2206,17 +2213,29 @@ long long ep_net_accept(long long server_fd) { long long ep_net_send(long long fd, const char* data) { if (!data) return 0; - /* send() may write fewer bytes than requested (partial write under load/ - backpressure). A single send() therefore silently truncated large IPC - responses, cutting agent replies mid-stream. Loop until all bytes are sent. */ - size_t total = strlen(data); - size_t off = 0; - while (off < total) { - ssize_t n = send((int)fd, data + off, total - off, 0); + return ep_net_send_raw(fd, (long long)data, (long long)strlen(data)); +} + +long long ep_net_send_raw(long long fd, long long data_ptr, long long count) { + if (data_ptr == 0 || count <= 0) return 0; + + /* send() may write fewer bytes than requested. Keep sending until the + explicit byte count is exhausted, including bytes after embedded NULs. */ + const char* data = (const char*)data_ptr; + long long off = 0; + while (off < count) { +#ifdef _WIN32 + int chunk = count - off > INT_MAX ? INT_MAX : (int)(count - off); + int n = send((int)fd, data + off, chunk, 0); + if (n < 0 && WSAGetLastError() == WSAEINTR) continue; +#else + ssize_t n = send((int)fd, data + off, (size_t)(count - off), 0); + if (n < 0 && errno == EINTR) continue; +#endif if (n <= 0) break; - off += (size_t)n; + off += (long long)n; } - return (long long)off; + return off; } char* ep_net_recv(long long fd, long long max_len) { @@ -4823,7 +4842,6 @@ long long ep_get_args(void) { return list_ptr; } - /* Built-in: string concatenation */ long long concat(long long a, long long b) { const char* sa = (const char*)a; @@ -5246,6 +5264,7 @@ long long dec_borrow_count(long long, long long, long long); long long inc_borrow_count(long long, long long, long long); long long check_safety_stmts(long long, long long, long long, long long, long long, long long, long long, long long, long long, long long); long long analyze_safety(long long, long long); +long long validate_program_safety(long long); long long generate_c(long long, long long); long long ep_rt_core_0(); long long ep_rt_core_1(); @@ -5765,9 +5784,11 @@ long long _main() { long long empty_imports = 0; long long program_ast = 0; long long check_ok = 0; + long long safety_ok = 0; long long opt_ok = 0; long long c_code = 0; long long c_path = 0; + long long c_compiler = 0; long long compile_cmd = 0; long long pf_len = 0; long long pf_idx = 0; @@ -5801,6 +5822,7 @@ long long _main() { ep_gc_push_root(&program_ast); ep_gc_push_root(&c_code); ep_gc_push_root(&c_path); + ep_gc_push_root(&c_compiler); ep_gc_push_root(&compile_cmd); ep_gc_push_root(&pf_idx); ep_gc_push_root(&pf); @@ -5895,6 +5917,12 @@ long long _main() { goto L_cleanup; } if (check_only == 1LL) { + safety_ok = validate_program_safety(program_ast); + if (safety_ok == 0LL) { + printf("%s\n", (char*)(long long)"Compilation failed: ownership/safety errors."); + ret_val = 1LL; + goto L_cleanup; + } printf("%s\n", (char*)(long long)"Check passed: no errors."); ret_val = 0LL; goto L_cleanup; @@ -5909,8 +5937,12 @@ long long _main() { } c_path = string_concat(stem, (long long)"_compiled.c"); ok = write_file_content((char*)c_path, (char*)c_code); - printf("%s\n", (char*)(long long)"[3/3] Compiling and Linking via Clang..."); - compile_cmd = (long long)"clang "; + c_compiler = (long long)"clang"; + if (ep_system((long long)"command -v clang >/dev/null 2>&1") != 0LL) { + c_compiler = (long long)"gcc"; + } + printf("%s\n", (char*)concat((long long)"[3/3] Compiling and Linking via ", concat(ep_auto_to_string(c_compiler), (long long)"..."))); + compile_cmd = string_concat(c_compiler, (long long)" "); compile_cmd = string_concat(compile_cmd, c_path); compile_cmd = string_concat(compile_cmd, (long long)" -o "); compile_cmd = string_concat(compile_cmd, stem); @@ -5958,7 +5990,7 @@ long long _main() { goto L_cleanup; } L_cleanup: - ep_gc_pop_roots(30); + ep_gc_pop_roots(31); return ret_val; } @@ -13612,6 +13644,7 @@ long long analyze_return_types(long long state, long long program) { ok = map_put(keys, values, (long long)"ep_net_listen", 1LL); ok = map_put(keys, values, (long long)"ep_net_accept", 1LL); ok = map_put(keys, values, (long long)"ep_net_send", 1LL); + ok = map_put(keys, values, (long long)"ep_net_send_raw", 1LL); ok = map_put(keys, values, (long long)"ep_net_recv", 3LL); ok = map_put(keys, values, (long long)"ep_net_close", 1LL); ok = map_put(keys, values, (long long)"append_list", 1LL); @@ -14032,6 +14065,10 @@ long long infer_type(long long state, long long expr, long long var_keys, long l ret_val = infer_type(state, inner, var_keys, var_values); goto L_cleanup; } + if (((type == 24LL || type == 26LL) || type == 35LL)) { + ret_val = 4LL; + goto L_cleanup; + } ret_val = 1LL; goto L_cleanup; L_cleanup: @@ -17302,6 +17339,25 @@ long long analyze_safety(long long state, long long program) { return ret_val; } +long long validate_program_safety(long long program) { + long long state = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&state); + ep_gc_push_root(&program); + ep_gc_maybe_collect(); + + state = create_codegen_state(); + ok = analyze_return_types(state, program); + ok = collect_prim_param_flags(state, program); + ret_val = analyze_safety(state, program); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(2); + return ret_val; +} + long long generate_c(long long program, long long is_test_mode) { long long state = 0; long long ok = 0; @@ -18028,6 +18084,7 @@ long long ep_rt_core_0() { ok = append_list(lines, (long long)"#define pthread_detach(t) ((void)(t), 0)\n"); ok = append_list(lines, (long long)"#else\n"); ok = append_list(lines, (long long)"#include \n"); + ok = append_list(lines, (long long)"#include \n"); ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)"#include \n"); ok = append_list(lines, (long long)"#include \n"); @@ -18138,7 +18195,6 @@ long long ep_rt_core_0() { ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); - ok = append_list(lines, (long long)"#if defined(__wasm__)\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18155,6 +18211,7 @@ long long ep_rt_core_1() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"#if defined(__wasm__)\n"); ok = append_list(lines, (long long)" typedef int ep_thread_t;\n"); ok = append_list(lines, (long long)" typedef int ep_mutex_t;\n"); ok = append_list(lines, (long long)" typedef int ep_cond_t;\n"); @@ -18304,7 +18361,6 @@ long long ep_rt_core_1() { ok = append_list(lines, (long long)" long long expiry = ep_time_now_ms() + timeout_ms;\n"); ok = append_list(lines, (long long)" EpTimer* timer = (EpTimer*)malloc(sizeof(EpTimer));\n"); ok = append_list(lines, (long long)" timer->expiry_ms = expiry;\n"); - ok = append_list(lines, (long long)" timer->task = task;\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18321,6 +18377,7 @@ long long ep_rt_core_2() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" timer->task = task;\n"); ok = append_list(lines, (long long)" timer->next = NULL;\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)" /* Insert sorted */\n"); @@ -18470,7 +18527,6 @@ long long ep_rt_core_2() { ok = append_list(lines, (long long)" if (task) {\n"); ok = append_list(lines, (long long)" if (task->is_cancelled) {\n"); ok = append_list(lines, (long long)" if (task->fut) {\n"); - ok = append_list(lines, (long long)" task->fut->completed = 1;\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18487,6 +18543,7 @@ long long ep_rt_core_3() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" task->fut->completed = 1;\n"); ok = append_list(lines, (long long)" task->fut->value = -1;\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" free(task->args);\n"); @@ -18636,7 +18693,6 @@ long long ep_rt_core_3() { ok = append_list(lines, (long long)" free(task->args);\n"); ok = append_list(lines, (long long)" free(task);\n"); ok = append_list(lines, (long long)" } else {\n"); - ok = append_list(lines, (long long)" EpTask* saved_current = ep_current_task;\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18653,6 +18709,7 @@ long long ep_rt_core_4() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" EpTask* saved_current = ep_current_task;\n"); ok = append_list(lines, (long long)" ep_current_task = task;\n"); ok = append_list(lines, (long long)" long long res = task->step(task->args);\n"); ok = append_list(lines, (long long)" ep_current_task = saved_current;\n"); @@ -18802,7 +18859,6 @@ long long ep_rt_core_4() { ok = append_list(lines, (long long)" } else {\n"); ok = append_list(lines, (long long)" ep_async_wait_step(timeout);\n"); ok = append_list(lines, (long long)" }\n"); - ok = append_list(lines, (long long)" }\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18819,6 +18875,7 @@ long long ep_rt_core_5() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" \n"); ok = append_list(lines, (long long)" return fut->value;\n"); @@ -18968,7 +19025,6 @@ long long ep_rt_core_5() { ok = append_list(lines, (long long)"/* Stop-the-world coordination. The collector sets ep_gc_stop_requested and, in\n"); ok = append_list(lines, (long long)" ep_gc_stop_the_world(), waits until every *other* registered thread has parked\n"); ok = append_list(lines, (long long)" at a safepoint (ep_gc_park_if_stopped). This guarantees mark/sweep never runs\n"); - ok = append_list(lines, (long long)" concurrently with a mutator changing its roots or an object's fields — the\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -18985,6 +19041,7 @@ long long ep_rt_core_6() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" concurrently with a mutator changing its roots or an object's fields — the\n"); ok = append_list(lines, (long long)" \"marking races with running mutators\" hazard. All three fields are touched\n"); ok = append_list(lines, (long long)" only while holding ep_gc_mutex (the lock-free reads of ep_gc_stop_requested at\n"); ok = append_list(lines, (long long)" safepoints are a benign optimization: a missed set just defers parking to the\n"); @@ -19134,7 +19191,6 @@ long long ep_rt_core_6() { ok = append_list(lines, (long long)" slot = i;\n"); ok = append_list(lines, (long long)" break;\n"); ok = append_list(lines, (long long)" }\n"); - ok = append_list(lines, (long long)" }\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19151,6 +19207,7 @@ long long ep_rt_core_7() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" if (slot == -1 && ep_num_threads < EP_MAX_THREADS) {\n"); ok = append_list(lines, (long long)" slot = ep_num_threads++;\n"); ok = append_list(lines, (long long)" }\n"); @@ -19300,7 +19357,6 @@ long long ep_rt_core_7() { ok = append_list(lines, (long long)" pthread_mutex_unlock(&ep_gc_mutex);\n"); ok = append_list(lines, (long long)" return NULL;\n"); ok = append_list(lines, (long long)" }\n"); - ok = append_list(lines, (long long)" obj->kind = kind;\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19317,6 +19373,7 @@ long long ep_rt_core_8() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" obj->kind = kind;\n"); ok = append_list(lines, (long long)" obj->marked = 0;\n"); ok = append_list(lines, (long long)" obj->ptr = ptr;\n"); ok = append_list(lines, (long long)" obj->size = 0;\n"); @@ -19466,7 +19523,6 @@ long long ep_rt_core_8() { ok = append_list(lines, (long long)" cross-thread stack read on the frequent minor path either. The expensive\n"); ok = append_list(lines, (long long)" full-stack scan is paid only on the rarer major collection, where it pins\n"); ok = append_list(lines, (long long)" any long-lived object reachable only via a register across many GCs.\n"); - ok = append_list(lines, (long long)"\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19483,6 +19539,7 @@ long long ep_rt_core_9() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)" Marked no_sanitize_address: a conservative scan deliberately reads whole stack\n"); ok = append_list(lines, (long long)" ranges (including ASAN redzones and out-of-frame slots), which is not a bug. */\n"); ok = append_list(lines, (long long)"#if defined(__SANITIZE_ADDRESS__)\n"); @@ -19632,7 +19689,6 @@ long long ep_rt_core_9() { ok = append_list(lines, (long long)"static void ep_gc_mark_minor(void) {\n"); ok = append_list(lines, (long long)" /* Conservatively scan our OWN live C stack first, to catch freshly-allocated argument\n"); ok = append_list(lines, (long long)" temporaries (only on the stack / in registers, not yet on the shadow stack) that a\n"); - ok = append_list(lines, (long long)" minor collection mid-expression would otherwise free. Own-thread only, so race-free. */\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19649,6 +19705,7 @@ long long ep_rt_core_10() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)" minor collection mid-expression would otherwise free. Own-thread only, so race-free. */\n"); ok = append_list(lines, (long long)" ep_gc_scan_own_stack_minor();\n"); ok = append_list(lines, (long long)" for (int t = 0; t < ep_num_threads; t++) {\n"); ok = append_list(lines, (long long)" if (!ep_thread_active[t]) continue;\n"); @@ -19798,7 +19855,6 @@ long long ep_rt_core_10() { ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" ep_gc_remembered_size = 0;\n"); ok = append_list(lines, (long long)"}\n"); - ok = append_list(lines, (long long)"\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19815,6 +19871,7 @@ long long ep_rt_core_11() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"static void ep_gc_collect_minor(void) {\n"); ok = append_list(lines, (long long)" if (!ep_gc_enabled) return;\n"); ok = append_list(lines, (long long)" ep_gc_minor_count++;\n"); @@ -19963,8 +20020,7 @@ long long ep_rt_core_11() { ok = append_list(lines, (long long)"long long json_get_bool(long long json_val, long long key_val);\n"); ok = append_list(lines, (long long)"long long ep_sha1(long long data_val);\n"); ok = append_list(lines, (long long)"long long ep_net_recv_bytes(long long fd, long long count);\n"); - ok = append_list(lines, (long long)"long long channel_try_recv(long long chan_ptr, long long out_ptr);\n"); - ok = append_list(lines, (long long)"long long channel_has_data(long long chan_ptr);\n"); + ok = append_list(lines, (long long)"long long ep_net_send_raw(long long fd, long long data_ptr, long long count);\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -19981,6 +20037,8 @@ long long ep_rt_core_12() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"long long channel_try_recv(long long chan_ptr, long long out_ptr);\n"); + ok = append_list(lines, (long long)"long long channel_has_data(long long chan_ptr);\n"); ok = append_list(lines, (long long)"long long channel_select(long long channels_list, long long timeout_ms);\n"); ok = append_list(lines, (long long)"long long ep_auto_to_string(long long val);\n"); ok = append_list(lines, (long long)"long long ep_float_to_string(long long bits);\n"); @@ -20129,8 +20187,6 @@ long long ep_rt_core_12() { ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"// Check if channel has data without consuming it\n"); - ok = append_list(lines, (long long)"long long channel_has_data(long long chan_ptr) {\n"); - ok = append_list(lines, (long long)" EpChannel* chan = (EpChannel*)chan_ptr;\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -20147,6 +20203,8 @@ long long ep_rt_core_13() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"long long channel_has_data(long long chan_ptr) {\n"); + ok = append_list(lines, (long long)" EpChannel* chan = (EpChannel*)chan_ptr;\n"); ok = append_list(lines, (long long)" if (!chan) return 0;\n"); ok = append_list(lines, (long long)" ep_mutex_lock(&chan->mutex);\n"); ok = append_list(lines, (long long)" int has = (chan->size > 0) ? 1 : 0;\n"); @@ -20267,6 +20325,11 @@ long long ep_rt_core_13() { ok = append_list(lines, (long long)" return 0;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)"long long ep_net_send_raw(long long fd, long long data_ptr, long long count) {\n"); + ok = append_list(lines, (long long)" (void)fd; (void)data_ptr; (void)count;\n"); + ok = append_list(lines, (long long)" return 0;\n"); + ok = append_list(lines, (long long)"}\n"); + ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"char* ep_net_recv(long long fd, long long max_len) {\n"); ok = append_list(lines, (long long)" (void)fd; (void)max_len;\n"); ok = append_list(lines, (long long)" char* empty = malloc(1);\n"); @@ -20290,13 +20353,6 @@ long long ep_rt_core_13() { ok = append_list(lines, (long long)"long long ep_system(long long cmd) {\n"); ok = append_list(lines, (long long)" (void)cmd;\n"); ok = append_list(lines, (long long)" return -1;\n"); - ok = append_list(lines, (long long)"}\n"); - ok = append_list(lines, (long long)"\n"); - ok = append_list(lines, (long long)"long long ep_play_sound(long long path) {\n"); - ok = append_list(lines, (long long)" (void)path;\n"); - ok = append_list(lines, (long long)" return -1;\n"); - ok = append_list(lines, (long long)"}\n"); - ok = append_list(lines, (long long)"\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -20313,6 +20369,13 @@ long long ep_rt_core_14() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"}\n"); + ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)"long long ep_play_sound(long long path) {\n"); + ok = append_list(lines, (long long)" (void)path;\n"); + ok = append_list(lines, (long long)" return -1;\n"); + ok = append_list(lines, (long long)"}\n"); + ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long ep_dlopen(long long path) {\n"); ok = append_list(lines, (long long)" (void)path;\n"); ok = append_list(lines, (long long)" return 0;\n"); @@ -20421,17 +20484,29 @@ long long ep_rt_core_14() { ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long ep_net_send(long long fd, const char* data) {\n"); ok = append_list(lines, (long long)" if (!data) return 0;\n"); - ok = append_list(lines, (long long)" /* send() may write fewer bytes than requested (partial write under load/\n"); - ok = append_list(lines, (long long)" backpressure). A single send() therefore silently truncated large IPC\n"); - ok = append_list(lines, (long long)" responses, cutting agent replies mid-stream. Loop until all bytes are sent. */\n"); - ok = append_list(lines, (long long)" size_t total = strlen(data);\n"); - ok = append_list(lines, (long long)" size_t off = 0;\n"); - ok = append_list(lines, (long long)" while (off < total) {\n"); - ok = append_list(lines, (long long)" ssize_t n = send((int)fd, data + off, total - off, 0);\n"); + ok = append_list(lines, (long long)" return ep_net_send_raw(fd, (long long)data, (long long)strlen(data));\n"); + ok = append_list(lines, (long long)"}\n"); + ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)"long long ep_net_send_raw(long long fd, long long data_ptr, long long count) {\n"); + ok = append_list(lines, (long long)" if (data_ptr == 0 || count <= 0) return 0;\n"); + ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)" /* send() may write fewer bytes than requested. Keep sending until the\n"); + ok = append_list(lines, (long long)" explicit byte count is exhausted, including bytes after embedded NULs. */\n"); + ok = append_list(lines, (long long)" const char* data = (const char*)data_ptr;\n"); + ok = append_list(lines, (long long)" long long off = 0;\n"); + ok = append_list(lines, (long long)" while (off < count) {\n"); + ok = append_list(lines, (long long)"#ifdef _WIN32\n"); + ok = append_list(lines, (long long)" int chunk = count - off > INT_MAX ? INT_MAX : (int)(count - off);\n"); + ok = append_list(lines, (long long)" int n = send((int)fd, data + off, chunk, 0);\n"); + ok = append_list(lines, (long long)" if (n < 0 && WSAGetLastError() == WSAEINTR) continue;\n"); + ok = append_list(lines, (long long)"#else\n"); + ok = append_list(lines, (long long)" ssize_t n = send((int)fd, data + off, (size_t)(count - off), 0);\n"); + ok = append_list(lines, (long long)" if (n < 0 && errno == EINTR) continue;\n"); + ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)" if (n <= 0) break;\n"); - ok = append_list(lines, (long long)" off += (size_t)n;\n"); + ok = append_list(lines, (long long)" off += (long long)n;\n"); ok = append_list(lines, (long long)" }\n"); - ok = append_list(lines, (long long)" return (long long)off;\n"); + ok = append_list(lines, (long long)" return off;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"char* ep_net_recv(long long fd, long long max_len) {\n"); @@ -20444,6 +20519,22 @@ long long ep_rt_core_14() { ok = append_list(lines, (long long)"#ifdef _WIN32\n"); ok = append_list(lines, (long long)" int n = recv((int)fd, buf, (int)max_len, 0);\n"); ok = append_list(lines, (long long)"#else\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_15() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" ssize_t n = recv((int)fd, buf, max_len, 0);\n"); ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)" if (n < 0) n = 0;\n"); @@ -20463,22 +20554,6 @@ long long ep_rt_core_14() { ok = append_list(lines, (long long)"#ifdef _WIN32\n"); ok = append_list(lines, (long long)" Sleep((DWORD)ms);\n"); ok = append_list(lines, (long long)"#else\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_15() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" usleep((useconds_t)(ms * 1000));\n"); ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)" return 0;\n"); @@ -20610,6 +20685,22 @@ long long ep_rt_core_15() { ok = append_list(lines, (long long)"typedef double (*ep_ff4)(double, double, double, double);\n"); ok = append_list(lines, (long long)"typedef double (*ep_ff5)(double, double, double, double, double);\n"); ok = append_list(lines, (long long)"typedef double (*ep_ff6)(double, double, double, double, double, double);\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_16() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"/* Call functions that take doubles and return double */\n"); ok = append_list(lines, (long long)"long long ep_dlcall_f0(long long fptr) {\n"); @@ -20629,22 +20720,6 @@ long long ep_rt_core_15() { ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"long long ep_dlcall_f5(long long fptr, long long a0, long long a1, long long a2, long long a3, long long a4) {\n"); ok = append_list(lines, (long long)" return ep_double_to_ll(((ep_ff5)fptr)(ep_ll_to_double(a0), ep_ll_to_double(a1), ep_ll_to_double(a2), ep_ll_to_double(a3), ep_ll_to_double(a4)));\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_16() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"long long ep_dlcall_f6(long long fptr, long long a0, long long a1, long long a2, long long a3, long long a4, long long a5) {\n"); ok = append_list(lines, (long long)" return ep_double_to_ll(((ep_ff6)fptr)(ep_ll_to_double(a0), ep_ll_to_double(a1), ep_ll_to_double(a2), ep_ll_to_double(a3), ep_ll_to_double(a4), ep_ll_to_double(a5)));\n"); @@ -20776,6 +20851,22 @@ long long ep_rt_core_16() { ok = append_list(lines, (long long)" const char* key = ep_map_key_str(key_val, keybuf, sizeof(keybuf));\n"); ok = append_list(lines, (long long)" if (!map) return 0;\n"); ok = append_list(lines, (long long)" if (map->size * 2 >= map->capacity) {\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_17() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" map_resize(map, map->capacity * 2);\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" unsigned long h = hash_string(key) % map->capacity;\n"); @@ -20795,22 +20886,6 @@ long long ep_rt_core_16() { ok = append_list(lines, (long long)" return value;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_17() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"long long map_get_val(long long map_ptr, long long key_val) {\n"); ok = append_list(lines, (long long)" if (EP_BADPTR(map_ptr)) return 0;\n"); ok = append_list(lines, (long long)" EpMap* map = (EpMap*)map_ptr;\n"); @@ -20942,6 +21017,22 @@ long long ep_rt_core_17() { ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"typedef struct {\n"); ok = append_list(lines, (long long)" long long* data;\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_18() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" long long capacity;\n"); ok = append_list(lines, (long long)" long long head;\n"); ok = append_list(lines, (long long)" long long tail;\n"); @@ -20961,22 +21052,6 @@ long long ep_rt_core_17() { ok = append_list(lines, (long long)" return 0;\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" return (long long)dq;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_18() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"static void deque_resize(EpDeque* dq, long long new_capacity) {\n"); @@ -21108,6 +21183,22 @@ long long ep_rt_core_18() { ok = append_list(lines, (long long)" if (!path) return 0;\n"); ok = append_list(lines, (long long)" struct stat st;\n"); ok = append_list(lines, (long long)" return stat(path, &st) == 0 ? 1 : 0;\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_19() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long fs_is_dir(long long path_val) {\n"); @@ -21127,22 +21218,6 @@ long long ep_rt_core_18() { ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long fs_get_size(long long path_val) {\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_19() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" const char* path = (const char*)path_val;\n"); ok = append_list(lines, (long long)" if (!path) return 0;\n"); ok = append_list(lines, (long long)" struct stat st;\n"); @@ -21274,25 +21349,6 @@ long long ep_rt_core_19() { ok = append_list(lines, (long long)" unsigned int datalen;\n"); ok = append_list(lines, (long long)" unsigned long long bitlen;\n"); ok = append_list(lines, (long long)" unsigned int state[8];\n"); - ok = append_list(lines, (long long)"} EP_SHA256_CTX;\n"); - ok = append_list(lines, (long long)"\n"); - ok = append_list(lines, (long long)"static const unsigned int sha256_k[64] = {\n"); - ok = append_list(lines, (long long)" 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,\n"); - ok = append_list(lines, (long long)" 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,\n"); - ok = append_list(lines, (long long)" 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,\n"); - ok = append_list(lines, (long long)" 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,\n"); - ok = append_list(lines, (long long)" 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,\n"); - ok = append_list(lines, (long long)" 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,\n"); - ok = append_list(lines, (long long)" 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,\n"); - ok = append_list(lines, (long long)" 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2\n"); - ok = append_list(lines, (long long)"};\n"); - ok = append_list(lines, (long long)"\n"); - ok = append_list(lines, (long long)"void ep_sha256_transform(EP_SHA256_CTX *ctx, const unsigned char *data) {\n"); - ok = append_list(lines, (long long)" unsigned int a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];\n"); - ok = append_list(lines, (long long)" for (i = 0, j = 0; i < 16; ++i, j += 4)\n"); - ok = append_list(lines, (long long)" m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);\n"); - ok = append_list(lines, (long long)" for ( ; i < 64; ++i)\n"); - ok = append_list(lines, (long long)" m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: @@ -21309,6 +21365,25 @@ long long ep_rt_core_20() { ep_gc_maybe_collect(); lines = create_list(); + ok = append_list(lines, (long long)"} EP_SHA256_CTX;\n"); + ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)"static const unsigned int sha256_k[64] = {\n"); + ok = append_list(lines, (long long)" 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,\n"); + ok = append_list(lines, (long long)" 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,\n"); + ok = append_list(lines, (long long)" 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,\n"); + ok = append_list(lines, (long long)" 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,\n"); + ok = append_list(lines, (long long)" 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,\n"); + ok = append_list(lines, (long long)" 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,\n"); + ok = append_list(lines, (long long)" 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,\n"); + ok = append_list(lines, (long long)" 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2\n"); + ok = append_list(lines, (long long)"};\n"); + ok = append_list(lines, (long long)"\n"); + ok = append_list(lines, (long long)"void ep_sha256_transform(EP_SHA256_CTX *ctx, const unsigned char *data) {\n"); + ok = append_list(lines, (long long)" unsigned int a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];\n"); + ok = append_list(lines, (long long)" for (i = 0, j = 0; i < 16; ++i, j += 4)\n"); + ok = append_list(lines, (long long)" m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);\n"); + ok = append_list(lines, (long long)" for ( ; i < 64; ++i)\n"); + ok = append_list(lines, (long long)" m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];\n"); ok = append_list(lines, (long long)" a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];\n"); ok = append_list(lines, (long long)" e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];\n"); ok = append_list(lines, (long long)" for (i = 0; i < 64; ++i) {\n"); @@ -21440,6 +21515,22 @@ long long ep_rt_core_20() { ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"typedef struct {\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_21() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" unsigned int count[2];\n"); ok = append_list(lines, (long long)" unsigned int state[4];\n"); ok = append_list(lines, (long long)" unsigned char buffer[64];\n"); @@ -21459,22 +21550,6 @@ long long ep_rt_core_20() { ok = append_list(lines, (long long)"#define GG(a,b,c,d,x,s,ac) { \\\n"); ok = append_list(lines, (long long)" (a) += G((b),(c),(d)) + (x) + (ac); \\\n"); ok = append_list(lines, (long long)" (a) = ROTATE_LEFT((a),(s)); \\\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_21() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" (a) += (b); \\\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"#define HH(a,b,c,d,x,s,ac) { \\\n"); @@ -21606,6 +21681,22 @@ long long ep_rt_core_21() { ok = append_list(lines, (long long)"long long string_length(const char* s) {\n"); ok = append_list(lines, (long long)" if (!s) return 0;\n"); ok = append_list(lines, (long long)" return strlen(s);\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_22() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long get_character(const char* s, long long index) {\n"); @@ -21625,22 +21716,6 @@ long long ep_rt_core_21() { ok = append_list(lines, (long long)" return (long long)list;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_22() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"long long get_list_data_ptr(long long list_ptr) {\n"); ok = append_list(lines, (long long)" if (EP_BADPTR(list_ptr)) return 0;\n"); ok = append_list(lines, (long long)" EpList* list = (EpList*)list_ptr;\n"); @@ -21772,6 +21847,22 @@ long long ep_rt_core_22() { ok = append_list(lines, (long long)"long long ep_sqlite3_column_count(long long stmt) {\n"); ok = append_list(lines, (long long)" return (long long)sqlite3_column_count((sqlite3_stmt*)stmt);\n"); ok = append_list(lines, (long long)"}\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_23() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long ep_sqlite3_column_text(long long stmt, long long col) {\n"); ok = append_list(lines, (long long)" const unsigned char* t = sqlite3_column_text((sqlite3_stmt*)stmt, (int)col);\n"); @@ -21791,22 +21882,6 @@ long long ep_rt_core_22() { ok = append_list(lines, (long long)" return (long long)sqlite3_finalize((sqlite3_stmt*)stmt);\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"#endif /* EP_HAS_SQLITE */\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_23() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"int ep_argc = 0;\n"); ok = append_list(lines, (long long)"char** ep_argv = NULL;\n"); @@ -21938,6 +22013,22 @@ long long ep_rt_core_23() { ok = append_list(lines, (long long)" list->length -= 1;\n"); ok = append_list(lines, (long long)" return list->data[list->length];\n"); ok = append_list(lines, (long long)"}\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_24() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long remove_list(long long list_ptr, long long index) {\n"); ok = append_list(lines, (long long)" if (EP_BADPTR(list_ptr)) return 0;\n"); @@ -21957,22 +22048,6 @@ long long ep_rt_core_23() { ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"/* Write text with NO trailing newline, and flush at once — for drawing to\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_24() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" the screen where every byte's position matters (cursor moves, escape\n"); ok = append_list(lines, (long long)" codes, a full-screen frame). puts()/display_string would append a\n"); ok = append_list(lines, (long long)" newline and scroll a full-height frame; this does not. */\n"); @@ -22104,6 +22179,22 @@ long long ep_rt_core_24() { ok = append_list(lines, (long long)" return remove(path) == 0 ? 1 : 0;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_25() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"long long ep_remove_directory(long long path_ptr) {\n"); ok = append_list(lines, (long long)" const char* path = (const char*)path_ptr;\n"); ok = append_list(lines, (long long)" return rmdir(path) == 0 ? 1 : 0;\n"); @@ -22123,22 +22214,6 @@ long long ep_rt_core_24() { ok = append_list(lines, (long long)" char buf[8192];\n"); ok = append_list(lines, (long long)" size_t n;\n"); ok = append_list(lines, (long long)" while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) {\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_25() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" fwrite(buf, 1, n, fout);\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" fclose(fin);\n"); @@ -22270,6 +22345,22 @@ long long ep_rt_core_25() { ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"#ifdef __wasm__\n"); ok = append_list(lines, (long long)"long long ep_run_command(long long cmd_ptr) {\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_26() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" (void)cmd_ptr;\n"); ok = append_list(lines, (long long)" return (long long)\"Error: running external commands is not supported on WebAssembly\";\n"); ok = append_list(lines, (long long)"}\n"); @@ -22289,22 +22380,6 @@ long long ep_rt_core_25() { ok = append_list(lines, (long long)" result[total] = '\\0';\n"); ok = append_list(lines, (long long)" pclose(fp);\n"); ok = append_list(lines, (long long)" return (long long)result;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_26() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)"\n"); @@ -22436,6 +22511,22 @@ long long ep_rt_core_26() { ok = append_list(lines, (long long)"#endif\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"#ifdef _MSC_VER\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_27() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"long long ep_atomic_create(long long initial) {\n"); ok = append_list(lines, (long long)" volatile long long* a = (volatile long long*)malloc(sizeof(long long));\n"); ok = append_list(lines, (long long)" InterlockedExchange64(a, initial);\n"); @@ -22455,22 +22546,6 @@ long long ep_rt_core_26() { ok = append_list(lines, (long long)" return InterlockedExchangeAdd64((volatile long long*)a, -delta);\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"long long ep_atomic_cas(long long a, long long expected, long long desired) {\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_27() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" long long old = InterlockedCompareExchange64((volatile long long*)a, desired, expected);\n"); ok = append_list(lines, (long long)" return (old == expected) ? 1 : 0;\n"); ok = append_list(lines, (long long)"}\n"); @@ -22602,6 +22677,22 @@ long long ep_rt_core_27() { ok = append_list(lines, (long long)" pthread_mutex_destroy(&s->mutex);\n"); ok = append_list(lines, (long long)" pthread_cond_destroy(&s->cond);\n"); ok = append_list(lines, (long long)" free(s);\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_28() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" return 0;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); @@ -22621,22 +22712,6 @@ long long ep_rt_core_27() { ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long ep_condvar_broadcast(long long cv) {\n"); ok = append_list(lines, (long long)" return pthread_cond_broadcast((pthread_cond_t*)cv) == 0 ? 1 : 0;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_28() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long ep_condvar_destroy(long long cv) {\n"); @@ -22768,6 +22843,22 @@ long long ep_rt_core_28() { ok = append_list(lines, (long long)" out[j++] = (i + 2 < len) ? b64_table[n & 63] : '=';\n"); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" out[j] = '\\0';\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_29() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" return (long long)out;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); @@ -22787,22 +22878,6 @@ long long ep_rt_core_28() { ok = append_list(lines, (long long)"\n"); ok = append_list(lines, (long long)"long long file_read(long long path_val) {\n"); ok = append_list(lines, (long long)" const char* path = (const char*)path_val;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_29() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" if (!path) return (long long)strdup(\"\");\n"); ok = append_list(lines, (long long)" FILE* f = fopen(path, \"rb\");\n"); ok = append_list(lines, (long long)" if (!f) return (long long)strdup(\"\");\n"); @@ -22934,6 +23009,22 @@ long long ep_rt_core_29() { ok = append_list(lines, (long long)" return (long long)result;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_30() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)"long long string_split(long long s_val, long long delim_val) {\n"); ok = append_list(lines, (long long)" const char* s = (const char*)s_val;\n"); ok = append_list(lines, (long long)" const char* delim = (const char*)delim_val;\n"); @@ -22953,22 +23044,6 @@ long long ep_rt_core_29() { ok = append_list(lines, (long long)" if (!found) break;\n"); ok = append_list(lines, (long long)" p = found + dlen;\n"); ok = append_list(lines, (long long)" }\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_30() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" return list;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); @@ -23100,6 +23175,22 @@ long long ep_rt_core_30() { ok = append_list(lines, (long long)" else if (*p == '[') { depth++; p++; }\n"); ok = append_list(lines, (long long)" else if (*p == ']') { depth--; p++; }\n"); ok = append_list(lines, (long long)" else p++;\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_31() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" } else {\n"); ok = append_list(lines, (long long)" while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\\n') p++;\n"); @@ -23119,22 +23210,6 @@ long long ep_rt_core_30() { ok = append_list(lines, (long long)" const char* ks = p;\n"); ok = append_list(lines, (long long)" while (*p && *p != '\"') { if (*p == '\\\\') p++; p++; }\n"); ok = append_list(lines, (long long)" size_t klen = p - ks;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_31() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" if (*p == '\"') p++;\n"); ok = append_list(lines, (long long)" p = json_skip_ws(p);\n"); ok = append_list(lines, (long long)" if (*p == ':') p++;\n"); @@ -23266,6 +23341,22 @@ long long ep_rt_core_31() { ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" result[j] = '\\0';\n"); ok = append_list(lines, (long long)" ep_gc_register(result, EP_OBJ_STRING);\n"); + ret_val = join_strings(lines); + goto L_cleanup; +L_cleanup: + ep_gc_pop_roots(1); + return ret_val; +} + +long long ep_rt_core_32() { + long long lines = 0; + long long ok = 0; + long long ret_val = 0; + + ep_gc_push_root(&lines); + ep_gc_maybe_collect(); + + lines = create_list(); ok = append_list(lines, (long long)" return (long long)result;\n"); ok = append_list(lines, (long long)"}\n"); ok = append_list(lines, (long long)"\n"); @@ -23285,22 +23376,6 @@ long long ep_rt_core_31() { ok = append_list(lines, (long long)" int n = recv((int)fd, buf + total, (int)(count - total), 0);\n"); ok = append_list(lines, (long long)" if (n <= 0) break;\n"); ok = append_list(lines, (long long)" total += n;\n"); - ret_val = join_strings(lines); - goto L_cleanup; -L_cleanup: - ep_gc_pop_roots(1); - return ret_val; -} - -long long ep_rt_core_32() { - long long lines = 0; - long long ok = 0; - long long ret_val = 0; - - ep_gc_push_root(&lines); - ep_gc_maybe_collect(); - - lines = create_list(); ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)"#else\n"); ok = append_list(lines, (long long)" ssize_t total = 0;\n"); @@ -23325,7 +23400,6 @@ long long ep_rt_core_32() { ok = append_list(lines, (long long)" }\n"); ok = append_list(lines, (long long)" return list_ptr;\n"); ok = append_list(lines, (long long)"}\n"); - ok = append_list(lines, (long long)"\n"); ret_val = join_strings(lines); goto L_cleanup; L_cleanup: diff --git a/conformance/test_concurrency.ep b/conformance/test_concurrency.ep index d05e83a..513c1b2 100644 --- a/conformance/test_concurrency.ep +++ b/conformance/test_concurrency.ep @@ -1,10 +1,10 @@ # Conformance Test: Concurrency and Async -define worker with id and count: +define worker with id and count and done: set i to 0 repeat while i < count: - display concat("Worker " and concat(int_to_string(id) and concat(": iteration " and int_to_string(i)))) set i to i + 1 + send id * 10 + i to done return 0 define channel_producer with ch and count: @@ -16,17 +16,23 @@ define channel_producer with ch and count: define main: # Spawn concurrent tasks - spawn worker(1 and 3) - spawn worker(2 and 3) + set done to channel + spawn worker(1 and 3 and done) + spawn worker(2 and 3 and done) + set worker_one to receive from done + set worker_two to receive from done + set worker_total to worker_one + worker_two + display f"worker total: {worker_total}" # Channel communication set ch to channel spawn channel_producer(ch and 5) set i to 0 + set total to 0 repeat while i < 5: set val to receive from ch - display val + set total to total + val set i to i + 1 - + display f"channel total: {total}" return 0 diff --git a/conformance/test_concurrency.expected b/conformance/test_concurrency.expected new file mode 100644 index 0000000..b67f4ae --- /dev/null +++ b/conformance/test_concurrency.expected @@ -0,0 +1,2 @@ +worker total: 36 +channel total: 10 diff --git a/ep_codegen.ep b/ep_codegen.ep index eab55ae..d70bfee 100644 --- a/ep_codegen.ep +++ b/ep_codegen.ep @@ -1020,6 +1020,7 @@ define analyze_return_types with state and program: set ok to map_put(keys and values and "ep_net_listen" and 1) set ok to map_put(keys and values and "ep_net_accept" and 1) set ok to map_put(keys and values and "ep_net_send" and 1) + set ok to map_put(keys and values and "ep_net_send_raw" and 1) set ok to map_put(keys and values and "ep_net_recv" and 3) set ok to map_put(keys and values and "ep_net_close" and 1) set ok to map_put(keys and values and "append_list" and 1) # TYPE_INT = 1 @@ -1307,6 +1308,10 @@ define infer_type with state and expr and var_keys and var_values: if type == 21: # NODE_AWAIT set inner to get_list(expr and 1) return infer_type(state and inner and var_keys and var_values) + if type == 24 || type == 26 || type == 35: # STRUCT_CREATE / ENUM_CREATE / LIST_LITERAL + # All three are heap-backed aggregate values and participate in move + # checking like lists in the self-hosted compiler's coarse type model. + return 4 return 1 # Variable offset collection is no longer needed for C transpilation @@ -3401,6 +3406,15 @@ define analyze_safety with state and program: set idx to idx + 1 return 1 +# Run the code generator's safety analysis without emitting C. The self-hosted +# `epc check` command uses this so check-only mode has the same ownership +# guarantees as a full compilation. +define validate_program_safety with program: + set state to create_codegen_state() + set ok to analyze_return_types(state and program) + set ok to collect_prim_param_flags(state and program) + return analyze_safety(state and program) + define generate_c with program and is_test_mode: set state to create_codegen_state() set ok to analyze_return_types(state and program) @@ -3848,4 +3862,3 @@ define generate_c with program and is_test_mode: set oksp to set_list(lines and closure_slot and spliced) set c_code to join_strings(lines) return c_code - diff --git a/ep_runtime_gen.ep b/ep_runtime_gen.ep index f4c9155..f3131e6 100644 --- a/ep_runtime_gen.ep +++ b/ep_runtime_gen.ep @@ -43,6 +43,7 @@ define ep_rt_core_0: set ok to append_list(lines and "#define pthread_detach(t) ((void)(t), 0)\n") set ok to append_list(lines and "#else\n") set ok to append_list(lines and "#include \n") + set ok to append_list(lines and "#include \n") set ok to append_list(lines and "#endif\n") set ok to append_list(lines and "#include \n") set ok to append_list(lines and "#include \n") @@ -153,11 +154,11 @@ define ep_rt_core_0: set ok to append_list(lines and "#endif\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") - set ok to append_list(lines and "#if defined(__wasm__)\n") return join_strings(lines) define ep_rt_core_1: set lines to create_list() + set ok to append_list(lines and "#if defined(__wasm__)\n") set ok to append_list(lines and " typedef int ep_thread_t;\n") set ok to append_list(lines and " typedef int ep_mutex_t;\n") set ok to append_list(lines and " typedef int ep_cond_t;\n") @@ -307,11 +308,11 @@ define ep_rt_core_1: set ok to append_list(lines and " long long expiry = ep_time_now_ms() + timeout_ms;\n") set ok to append_list(lines and " EpTimer* timer = (EpTimer*)malloc(sizeof(EpTimer));\n") set ok to append_list(lines and " timer->expiry_ms = expiry;\n") - set ok to append_list(lines and " timer->task = task;\n") return join_strings(lines) define ep_rt_core_2: set lines to create_list() + set ok to append_list(lines and " timer->task = task;\n") set ok to append_list(lines and " timer->next = NULL;\n") set ok to append_list(lines and "\n") set ok to append_list(lines and " /* Insert sorted */\n") @@ -461,11 +462,11 @@ define ep_rt_core_2: set ok to append_list(lines and " if (task) {\n") set ok to append_list(lines and " if (task->is_cancelled) {\n") set ok to append_list(lines and " if (task->fut) {\n") - set ok to append_list(lines and " task->fut->completed = 1;\n") return join_strings(lines) define ep_rt_core_3: set lines to create_list() + set ok to append_list(lines and " task->fut->completed = 1;\n") set ok to append_list(lines and " task->fut->value = -1;\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " free(task->args);\n") @@ -615,11 +616,11 @@ define ep_rt_core_3: set ok to append_list(lines and " free(task->args);\n") set ok to append_list(lines and " free(task);\n") set ok to append_list(lines and " } else {\n") - set ok to append_list(lines and " EpTask* saved_current = ep_current_task;\n") return join_strings(lines) define ep_rt_core_4: set lines to create_list() + set ok to append_list(lines and " EpTask* saved_current = ep_current_task;\n") set ok to append_list(lines and " ep_current_task = task;\n") set ok to append_list(lines and " long long res = task->step(task->args);\n") set ok to append_list(lines and " ep_current_task = saved_current;\n") @@ -769,11 +770,11 @@ define ep_rt_core_4: set ok to append_list(lines and " } else {\n") set ok to append_list(lines and " ep_async_wait_step(timeout);\n") set ok to append_list(lines and " }\n") - set ok to append_list(lines and " }\n") return join_strings(lines) define ep_rt_core_5: set lines to create_list() + set ok to append_list(lines and " }\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " \n") set ok to append_list(lines and " return fut->value;\n") @@ -923,11 +924,11 @@ define ep_rt_core_5: set ok to append_list(lines and "/* Stop-the-world coordination. The collector sets ep_gc_stop_requested and, in\n") set ok to append_list(lines and " ep_gc_stop_the_world(), waits until every *other* registered thread has parked\n") set ok to append_list(lines and " at a safepoint (ep_gc_park_if_stopped). This guarantees mark/sweep never runs\n") - set ok to append_list(lines and " concurrently with a mutator changing its roots or an object's fields — the\n") return join_strings(lines) define ep_rt_core_6: set lines to create_list() + set ok to append_list(lines and " concurrently with a mutator changing its roots or an object's fields — the\n") set ok to append_list(lines and " \"marking races with running mutators\" hazard. All three fields are touched\n") set ok to append_list(lines and " only while holding ep_gc_mutex (the lock-free reads of ep_gc_stop_requested at\n") set ok to append_list(lines and " safepoints are a benign optimization: a missed set just defers parking to the\n") @@ -1077,11 +1078,11 @@ define ep_rt_core_6: set ok to append_list(lines and " slot = i;\n") set ok to append_list(lines and " break;\n") set ok to append_list(lines and " }\n") - set ok to append_list(lines and " }\n") return join_strings(lines) define ep_rt_core_7: set lines to create_list() + set ok to append_list(lines and " }\n") set ok to append_list(lines and " if (slot == -1 && ep_num_threads < EP_MAX_THREADS) {\n") set ok to append_list(lines and " slot = ep_num_threads++;\n") set ok to append_list(lines and " }\n") @@ -1231,11 +1232,11 @@ define ep_rt_core_7: set ok to append_list(lines and " pthread_mutex_unlock(&ep_gc_mutex);\n") set ok to append_list(lines and " return NULL;\n") set ok to append_list(lines and " }\n") - set ok to append_list(lines and " obj->kind = kind;\n") return join_strings(lines) define ep_rt_core_8: set lines to create_list() + set ok to append_list(lines and " obj->kind = kind;\n") set ok to append_list(lines and " obj->marked = 0;\n") set ok to append_list(lines and " obj->ptr = ptr;\n") set ok to append_list(lines and " obj->size = 0;\n") @@ -1385,11 +1386,11 @@ define ep_rt_core_8: set ok to append_list(lines and " cross-thread stack read on the frequent minor path either. The expensive\n") set ok to append_list(lines and " full-stack scan is paid only on the rarer major collection, where it pins\n") set ok to append_list(lines and " any long-lived object reachable only via a register across many GCs.\n") - set ok to append_list(lines and "\n") return join_strings(lines) define ep_rt_core_9: set lines to create_list() + set ok to append_list(lines and "\n") set ok to append_list(lines and " Marked no_sanitize_address: a conservative scan deliberately reads whole stack\n") set ok to append_list(lines and " ranges (including ASAN redzones and out-of-frame slots), which is not a bug. */\n") set ok to append_list(lines and "#if defined(__SANITIZE_ADDRESS__)\n") @@ -1539,11 +1540,11 @@ define ep_rt_core_9: set ok to append_list(lines and "static void ep_gc_mark_minor(void) {\n") set ok to append_list(lines and " /* Conservatively scan our OWN live C stack first, to catch freshly-allocated argument\n") set ok to append_list(lines and " temporaries (only on the stack / in registers, not yet on the shadow stack) that a\n") - set ok to append_list(lines and " minor collection mid-expression would otherwise free. Own-thread only, so race-free. */\n") return join_strings(lines) define ep_rt_core_10: set lines to create_list() + set ok to append_list(lines and " minor collection mid-expression would otherwise free. Own-thread only, so race-free. */\n") set ok to append_list(lines and " ep_gc_scan_own_stack_minor();\n") set ok to append_list(lines and " for (int t = 0; t < ep_num_threads; t++) {\n") set ok to append_list(lines and " if (!ep_thread_active[t]) continue;\n") @@ -1693,11 +1694,11 @@ define ep_rt_core_10: set ok to append_list(lines and " }\n") set ok to append_list(lines and " ep_gc_remembered_size = 0;\n") set ok to append_list(lines and "}\n") - set ok to append_list(lines and "\n") return join_strings(lines) define ep_rt_core_11: set lines to create_list() + set ok to append_list(lines and "\n") set ok to append_list(lines and "static void ep_gc_collect_minor(void) {\n") set ok to append_list(lines and " if (!ep_gc_enabled) return;\n") set ok to append_list(lines and " ep_gc_minor_count++;\n") @@ -1846,12 +1847,13 @@ define ep_rt_core_11: set ok to append_list(lines and "long long json_get_bool(long long json_val, long long key_val);\n") set ok to append_list(lines and "long long ep_sha1(long long data_val);\n") set ok to append_list(lines and "long long ep_net_recv_bytes(long long fd, long long count);\n") - set ok to append_list(lines and "long long channel_try_recv(long long chan_ptr, long long out_ptr);\n") - set ok to append_list(lines and "long long channel_has_data(long long chan_ptr);\n") + set ok to append_list(lines and "long long ep_net_send_raw(long long fd, long long data_ptr, long long count);\n") return join_strings(lines) define ep_rt_core_12: set lines to create_list() + set ok to append_list(lines and "long long channel_try_recv(long long chan_ptr, long long out_ptr);\n") + set ok to append_list(lines and "long long channel_has_data(long long chan_ptr);\n") set ok to append_list(lines and "long long channel_select(long long channels_list, long long timeout_ms);\n") set ok to append_list(lines and "long long ep_auto_to_string(long long val);\n") set ok to append_list(lines and "long long ep_float_to_string(long long bits);\n") @@ -2000,12 +2002,12 @@ define ep_rt_core_12: set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "// Check if channel has data without consuming it\n") - set ok to append_list(lines and "long long channel_has_data(long long chan_ptr) {\n") - set ok to append_list(lines and " EpChannel* chan = (EpChannel*)chan_ptr;\n") return join_strings(lines) define ep_rt_core_13: set lines to create_list() + set ok to append_list(lines and "long long channel_has_data(long long chan_ptr) {\n") + set ok to append_list(lines and " EpChannel* chan = (EpChannel*)chan_ptr;\n") set ok to append_list(lines and " if (!chan) return 0;\n") set ok to append_list(lines and " ep_mutex_lock(&chan->mutex);\n") set ok to append_list(lines and " int has = (chan->size > 0) ? 1 : 0;\n") @@ -2126,6 +2128,11 @@ define ep_rt_core_13: set ok to append_list(lines and " return 0;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") + set ok to append_list(lines and "long long ep_net_send_raw(long long fd, long long data_ptr, long long count) {\n") + set ok to append_list(lines and " (void)fd; (void)data_ptr; (void)count;\n") + set ok to append_list(lines and " return 0;\n") + set ok to append_list(lines and "}\n") + set ok to append_list(lines and "\n") set ok to append_list(lines and "char* ep_net_recv(long long fd, long long max_len) {\n") set ok to append_list(lines and " (void)fd; (void)max_len;\n") set ok to append_list(lines and " char* empty = malloc(1);\n") @@ -2149,6 +2156,10 @@ define ep_rt_core_13: set ok to append_list(lines and "long long ep_system(long long cmd) {\n") set ok to append_list(lines and " (void)cmd;\n") set ok to append_list(lines and " return -1;\n") + return join_strings(lines) + +define ep_rt_core_14: + set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "long long ep_play_sound(long long path) {\n") @@ -2156,10 +2167,6 @@ define ep_rt_core_13: set ok to append_list(lines and " return -1;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") - return join_strings(lines) - -define ep_rt_core_14: - set lines to create_list() set ok to append_list(lines and "long long ep_dlopen(long long path) {\n") set ok to append_list(lines and " (void)path;\n") set ok to append_list(lines and " return 0;\n") @@ -2268,17 +2275,29 @@ define ep_rt_core_14: set ok to append_list(lines and "\n") set ok to append_list(lines and "long long ep_net_send(long long fd, const char* data) {\n") set ok to append_list(lines and " if (!data) return 0;\n") - set ok to append_list(lines and " /* send() may write fewer bytes than requested (partial write under load/\n") - set ok to append_list(lines and " backpressure). A single send() therefore silently truncated large IPC\n") - set ok to append_list(lines and " responses, cutting agent replies mid-stream. Loop until all bytes are sent. */\n") - set ok to append_list(lines and " size_t total = strlen(data);\n") - set ok to append_list(lines and " size_t off = 0;\n") - set ok to append_list(lines and " while (off < total) {\n") - set ok to append_list(lines and " ssize_t n = send((int)fd, data + off, total - off, 0);\n") + set ok to append_list(lines and " return ep_net_send_raw(fd, (long long)data, (long long)strlen(data));\n") + set ok to append_list(lines and "}\n") + set ok to append_list(lines and "\n") + set ok to append_list(lines and "long long ep_net_send_raw(long long fd, long long data_ptr, long long count) {\n") + set ok to append_list(lines and " if (data_ptr == 0 || count <= 0) return 0;\n") + set ok to append_list(lines and "\n") + set ok to append_list(lines and " /* send() may write fewer bytes than requested. Keep sending until the\n") + set ok to append_list(lines and " explicit byte count is exhausted, including bytes after embedded NULs. */\n") + set ok to append_list(lines and " const char* data = (const char*)data_ptr;\n") + set ok to append_list(lines and " long long off = 0;\n") + set ok to append_list(lines and " while (off < count) {\n") + set ok to append_list(lines and "#ifdef _WIN32\n") + set ok to append_list(lines and " int chunk = count - off > INT_MAX ? INT_MAX : (int)(count - off);\n") + set ok to append_list(lines and " int n = send((int)fd, data + off, chunk, 0);\n") + set ok to append_list(lines and " if (n < 0 && WSAGetLastError() == WSAEINTR) continue;\n") + set ok to append_list(lines and "#else\n") + set ok to append_list(lines and " ssize_t n = send((int)fd, data + off, (size_t)(count - off), 0);\n") + set ok to append_list(lines and " if (n < 0 && errno == EINTR) continue;\n") + set ok to append_list(lines and "#endif\n") set ok to append_list(lines and " if (n <= 0) break;\n") - set ok to append_list(lines and " off += (size_t)n;\n") + set ok to append_list(lines and " off += (long long)n;\n") set ok to append_list(lines and " }\n") - set ok to append_list(lines and " return (long long)off;\n") + set ok to append_list(lines and " return off;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "char* ep_net_recv(long long fd, long long max_len) {\n") @@ -2291,6 +2310,10 @@ define ep_rt_core_14: set ok to append_list(lines and "#ifdef _WIN32\n") set ok to append_list(lines and " int n = recv((int)fd, buf, (int)max_len, 0);\n") set ok to append_list(lines and "#else\n") + return join_strings(lines) + +define ep_rt_core_15: + set lines to create_list() set ok to append_list(lines and " ssize_t n = recv((int)fd, buf, max_len, 0);\n") set ok to append_list(lines and "#endif\n") set ok to append_list(lines and " if (n < 0) n = 0;\n") @@ -2310,10 +2333,6 @@ define ep_rt_core_14: set ok to append_list(lines and "#ifdef _WIN32\n") set ok to append_list(lines and " Sleep((DWORD)ms);\n") set ok to append_list(lines and "#else\n") - return join_strings(lines) - -define ep_rt_core_15: - set lines to create_list() set ok to append_list(lines and " usleep((useconds_t)(ms * 1000));\n") set ok to append_list(lines and "#endif\n") set ok to append_list(lines and " return 0;\n") @@ -2445,6 +2464,10 @@ define ep_rt_core_15: set ok to append_list(lines and "typedef double (*ep_ff4)(double, double, double, double);\n") set ok to append_list(lines and "typedef double (*ep_ff5)(double, double, double, double, double);\n") set ok to append_list(lines and "typedef double (*ep_ff6)(double, double, double, double, double, double);\n") + return join_strings(lines) + +define ep_rt_core_16: + set lines to create_list() set ok to append_list(lines and "\n") set ok to append_list(lines and "/* Call functions that take doubles and return double */\n") set ok to append_list(lines and "long long ep_dlcall_f0(long long fptr) {\n") @@ -2464,10 +2487,6 @@ define ep_rt_core_15: set ok to append_list(lines and "}\n") set ok to append_list(lines and "long long ep_dlcall_f5(long long fptr, long long a0, long long a1, long long a2, long long a3, long long a4) {\n") set ok to append_list(lines and " return ep_double_to_ll(((ep_ff5)fptr)(ep_ll_to_double(a0), ep_ll_to_double(a1), ep_ll_to_double(a2), ep_ll_to_double(a3), ep_ll_to_double(a4)));\n") - return join_strings(lines) - -define ep_rt_core_16: - set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "long long ep_dlcall_f6(long long fptr, long long a0, long long a1, long long a2, long long a3, long long a4, long long a5) {\n") set ok to append_list(lines and " return ep_double_to_ll(((ep_ff6)fptr)(ep_ll_to_double(a0), ep_ll_to_double(a1), ep_ll_to_double(a2), ep_ll_to_double(a3), ep_ll_to_double(a4), ep_ll_to_double(a5)));\n") @@ -2599,6 +2618,10 @@ define ep_rt_core_16: set ok to append_list(lines and " const char* key = ep_map_key_str(key_val, keybuf, sizeof(keybuf));\n") set ok to append_list(lines and " if (!map) return 0;\n") set ok to append_list(lines and " if (map->size * 2 >= map->capacity) {\n") + return join_strings(lines) + +define ep_rt_core_17: + set lines to create_list() set ok to append_list(lines and " map_resize(map, map->capacity * 2);\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " unsigned long h = hash_string(key) % map->capacity;\n") @@ -2618,10 +2641,6 @@ define ep_rt_core_16: set ok to append_list(lines and " return value;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") - return join_strings(lines) - -define ep_rt_core_17: - set lines to create_list() set ok to append_list(lines and "long long map_get_val(long long map_ptr, long long key_val) {\n") set ok to append_list(lines and " if (EP_BADPTR(map_ptr)) return 0;\n") set ok to append_list(lines and " EpMap* map = (EpMap*)map_ptr;\n") @@ -2753,6 +2772,10 @@ define ep_rt_core_17: set ok to append_list(lines and "\n") set ok to append_list(lines and "typedef struct {\n") set ok to append_list(lines and " long long* data;\n") + return join_strings(lines) + +define ep_rt_core_18: + set lines to create_list() set ok to append_list(lines and " long long capacity;\n") set ok to append_list(lines and " long long head;\n") set ok to append_list(lines and " long long tail;\n") @@ -2772,10 +2795,6 @@ define ep_rt_core_17: set ok to append_list(lines and " return 0;\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " return (long long)dq;\n") - return join_strings(lines) - -define ep_rt_core_18: - set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "static void deque_resize(EpDeque* dq, long long new_capacity) {\n") @@ -2907,6 +2926,10 @@ define ep_rt_core_18: set ok to append_list(lines and " if (!path) return 0;\n") set ok to append_list(lines and " struct stat st;\n") set ok to append_list(lines and " return stat(path, &st) == 0 ? 1 : 0;\n") + return join_strings(lines) + +define ep_rt_core_19: + set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "long long fs_is_dir(long long path_val) {\n") @@ -2926,10 +2949,6 @@ define ep_rt_core_18: set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "long long fs_get_size(long long path_val) {\n") - return join_strings(lines) - -define ep_rt_core_19: - set lines to create_list() set ok to append_list(lines and " const char* path = (const char*)path_val;\n") set ok to append_list(lines and " if (!path) return 0;\n") set ok to append_list(lines and " struct stat st;\n") @@ -3061,6 +3080,10 @@ define ep_rt_core_19: set ok to append_list(lines and " unsigned int datalen;\n") set ok to append_list(lines and " unsigned long long bitlen;\n") set ok to append_list(lines and " unsigned int state[8];\n") + return join_strings(lines) + +define ep_rt_core_20: + set lines to create_list() set ok to append_list(lines and "} EP_SHA256_CTX;\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "static const unsigned int sha256_k[64] = {\n") @@ -3080,10 +3103,6 @@ define ep_rt_core_19: set ok to append_list(lines and " m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);\n") set ok to append_list(lines and " for ( ; i < 64; ++i)\n") set ok to append_list(lines and " m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];\n") - return join_strings(lines) - -define ep_rt_core_20: - set lines to create_list() set ok to append_list(lines and " a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];\n") set ok to append_list(lines and " e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];\n") set ok to append_list(lines and " for (i = 0; i < 64; ++i) {\n") @@ -3215,6 +3234,10 @@ define ep_rt_core_20: set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "typedef struct {\n") + return join_strings(lines) + +define ep_rt_core_21: + set lines to create_list() set ok to append_list(lines and " unsigned int count[2];\n") set ok to append_list(lines and " unsigned int state[4];\n") set ok to append_list(lines and " unsigned char buffer[64];\n") @@ -3234,10 +3257,6 @@ define ep_rt_core_20: set ok to append_list(lines and "#define GG(a,b,c,d,x,s,ac) { \\\n") set ok to append_list(lines and " (a) += G((b),(c),(d)) + (x) + (ac); \\\n") set ok to append_list(lines and " (a) = ROTATE_LEFT((a),(s)); \\\n") - return join_strings(lines) - -define ep_rt_core_21: - set lines to create_list() set ok to append_list(lines and " (a) += (b); \\\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "#define HH(a,b,c,d,x,s,ac) { \\\n") @@ -3369,6 +3388,10 @@ define ep_rt_core_21: set ok to append_list(lines and "long long string_length(const char* s) {\n") set ok to append_list(lines and " if (!s) return 0;\n") set ok to append_list(lines and " return strlen(s);\n") + return join_strings(lines) + +define ep_rt_core_22: + set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "long long get_character(const char* s, long long index) {\n") @@ -3388,10 +3411,6 @@ define ep_rt_core_21: set ok to append_list(lines and " return (long long)list;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") - return join_strings(lines) - -define ep_rt_core_22: - set lines to create_list() set ok to append_list(lines and "long long get_list_data_ptr(long long list_ptr) {\n") set ok to append_list(lines and " if (EP_BADPTR(list_ptr)) return 0;\n") set ok to append_list(lines and " EpList* list = (EpList*)list_ptr;\n") @@ -3523,6 +3542,10 @@ define ep_rt_core_22: set ok to append_list(lines and "long long ep_sqlite3_column_count(long long stmt) {\n") set ok to append_list(lines and " return (long long)sqlite3_column_count((sqlite3_stmt*)stmt);\n") set ok to append_list(lines and "}\n") + return join_strings(lines) + +define ep_rt_core_23: + set lines to create_list() set ok to append_list(lines and "\n") set ok to append_list(lines and "long long ep_sqlite3_column_text(long long stmt, long long col) {\n") set ok to append_list(lines and " const unsigned char* t = sqlite3_column_text((sqlite3_stmt*)stmt, (int)col);\n") @@ -3542,10 +3565,6 @@ define ep_rt_core_22: set ok to append_list(lines and " return (long long)sqlite3_finalize((sqlite3_stmt*)stmt);\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "#endif /* EP_HAS_SQLITE */\n") - return join_strings(lines) - -define ep_rt_core_23: - set lines to create_list() set ok to append_list(lines and "\n") set ok to append_list(lines and "int ep_argc = 0;\n") set ok to append_list(lines and "char** ep_argv = NULL;\n") @@ -3677,6 +3696,10 @@ define ep_rt_core_23: set ok to append_list(lines and " list->length -= 1;\n") set ok to append_list(lines and " return list->data[list->length];\n") set ok to append_list(lines and "}\n") + return join_strings(lines) + +define ep_rt_core_24: + set lines to create_list() set ok to append_list(lines and "\n") set ok to append_list(lines and "long long remove_list(long long list_ptr, long long index) {\n") set ok to append_list(lines and " if (EP_BADPTR(list_ptr)) return 0;\n") @@ -3696,10 +3719,6 @@ define ep_rt_core_23: set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "/* Write text with NO trailing newline, and flush at once — for drawing to\n") - return join_strings(lines) - -define ep_rt_core_24: - set lines to create_list() set ok to append_list(lines and " the screen where every byte's position matters (cursor moves, escape\n") set ok to append_list(lines and " codes, a full-screen frame). puts()/display_string would append a\n") set ok to append_list(lines and " newline and scroll a full-height frame; this does not. */\n") @@ -3831,6 +3850,10 @@ define ep_rt_core_24: set ok to append_list(lines and " return remove(path) == 0 ? 1 : 0;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") + return join_strings(lines) + +define ep_rt_core_25: + set lines to create_list() set ok to append_list(lines and "long long ep_remove_directory(long long path_ptr) {\n") set ok to append_list(lines and " const char* path = (const char*)path_ptr;\n") set ok to append_list(lines and " return rmdir(path) == 0 ? 1 : 0;\n") @@ -3850,10 +3873,6 @@ define ep_rt_core_24: set ok to append_list(lines and " char buf[8192];\n") set ok to append_list(lines and " size_t n;\n") set ok to append_list(lines and " while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) {\n") - return join_strings(lines) - -define ep_rt_core_25: - set lines to create_list() set ok to append_list(lines and " fwrite(buf, 1, n, fout);\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " fclose(fin);\n") @@ -3985,6 +4004,10 @@ define ep_rt_core_25: set ok to append_list(lines and "\n") set ok to append_list(lines and "#ifdef __wasm__\n") set ok to append_list(lines and "long long ep_run_command(long long cmd_ptr) {\n") + return join_strings(lines) + +define ep_rt_core_26: + set lines to create_list() set ok to append_list(lines and " (void)cmd_ptr;\n") set ok to append_list(lines and " return (long long)\"Error: running external commands is not supported on WebAssembly\";\n") set ok to append_list(lines and "}\n") @@ -4004,10 +4027,6 @@ define ep_rt_core_25: set ok to append_list(lines and " result[total] = '\\0';\n") set ok to append_list(lines and " pclose(fp);\n") set ok to append_list(lines and " return (long long)result;\n") - return join_strings(lines) - -define ep_rt_core_26: - set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "#endif\n") set ok to append_list(lines and "\n") @@ -4139,6 +4158,10 @@ define ep_rt_core_26: set ok to append_list(lines and "#endif\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "#ifdef _MSC_VER\n") + return join_strings(lines) + +define ep_rt_core_27: + set lines to create_list() set ok to append_list(lines and "long long ep_atomic_create(long long initial) {\n") set ok to append_list(lines and " volatile long long* a = (volatile long long*)malloc(sizeof(long long));\n") set ok to append_list(lines and " InterlockedExchange64(a, initial);\n") @@ -4158,10 +4181,6 @@ define ep_rt_core_26: set ok to append_list(lines and " return InterlockedExchangeAdd64((volatile long long*)a, -delta);\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "long long ep_atomic_cas(long long a, long long expected, long long desired) {\n") - return join_strings(lines) - -define ep_rt_core_27: - set lines to create_list() set ok to append_list(lines and " long long old = InterlockedCompareExchange64((volatile long long*)a, desired, expected);\n") set ok to append_list(lines and " return (old == expected) ? 1 : 0;\n") set ok to append_list(lines and "}\n") @@ -4293,6 +4312,10 @@ define ep_rt_core_27: set ok to append_list(lines and " pthread_mutex_destroy(&s->mutex);\n") set ok to append_list(lines and " pthread_cond_destroy(&s->cond);\n") set ok to append_list(lines and " free(s);\n") + return join_strings(lines) + +define ep_rt_core_28: + set lines to create_list() set ok to append_list(lines and " return 0;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") @@ -4312,10 +4335,6 @@ define ep_rt_core_27: set ok to append_list(lines and "\n") set ok to append_list(lines and "long long ep_condvar_broadcast(long long cv) {\n") set ok to append_list(lines and " return pthread_cond_broadcast((pthread_cond_t*)cv) == 0 ? 1 : 0;\n") - return join_strings(lines) - -define ep_rt_core_28: - set lines to create_list() set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") set ok to append_list(lines and "long long ep_condvar_destroy(long long cv) {\n") @@ -4447,6 +4466,10 @@ define ep_rt_core_28: set ok to append_list(lines and " out[j++] = (i + 2 < len) ? b64_table[n & 63] : '=';\n") set ok to append_list(lines and " }\n") set ok to append_list(lines and " out[j] = '\\0';\n") + return join_strings(lines) + +define ep_rt_core_29: + set lines to create_list() set ok to append_list(lines and " return (long long)out;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") @@ -4466,10 +4489,6 @@ define ep_rt_core_28: set ok to append_list(lines and "\n") set ok to append_list(lines and "long long file_read(long long path_val) {\n") set ok to append_list(lines and " const char* path = (const char*)path_val;\n") - return join_strings(lines) - -define ep_rt_core_29: - set lines to create_list() set ok to append_list(lines and " if (!path) return (long long)strdup(\"\");\n") set ok to append_list(lines and " FILE* f = fopen(path, \"rb\");\n") set ok to append_list(lines and " if (!f) return (long long)strdup(\"\");\n") @@ -4601,6 +4620,10 @@ define ep_rt_core_29: set ok to append_list(lines and " return (long long)result;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") + return join_strings(lines) + +define ep_rt_core_30: + set lines to create_list() set ok to append_list(lines and "long long string_split(long long s_val, long long delim_val) {\n") set ok to append_list(lines and " const char* s = (const char*)s_val;\n") set ok to append_list(lines and " const char* delim = (const char*)delim_val;\n") @@ -4620,10 +4643,6 @@ define ep_rt_core_29: set ok to append_list(lines and " if (!found) break;\n") set ok to append_list(lines and " p = found + dlen;\n") set ok to append_list(lines and " }\n") - return join_strings(lines) - -define ep_rt_core_30: - set lines to create_list() set ok to append_list(lines and " return list;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") @@ -4755,6 +4774,10 @@ define ep_rt_core_30: set ok to append_list(lines and " else if (*p == '[') { depth++; p++; }\n") set ok to append_list(lines and " else if (*p == ']') { depth--; p++; }\n") set ok to append_list(lines and " else p++;\n") + return join_strings(lines) + +define ep_rt_core_31: + set lines to create_list() set ok to append_list(lines and " }\n") set ok to append_list(lines and " } else {\n") set ok to append_list(lines and " while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\\n') p++;\n") @@ -4774,10 +4797,6 @@ define ep_rt_core_30: set ok to append_list(lines and " const char* ks = p;\n") set ok to append_list(lines and " while (*p && *p != '\"') { if (*p == '\\\\') p++; p++; }\n") set ok to append_list(lines and " size_t klen = p - ks;\n") - return join_strings(lines) - -define ep_rt_core_31: - set lines to create_list() set ok to append_list(lines and " if (*p == '\"') p++;\n") set ok to append_list(lines and " p = json_skip_ws(p);\n") set ok to append_list(lines and " if (*p == ':') p++;\n") @@ -4909,6 +4928,10 @@ define ep_rt_core_31: set ok to append_list(lines and " }\n") set ok to append_list(lines and " result[j] = '\\0';\n") set ok to append_list(lines and " ep_gc_register(result, EP_OBJ_STRING);\n") + return join_strings(lines) + +define ep_rt_core_32: + set lines to create_list() set ok to append_list(lines and " return (long long)result;\n") set ok to append_list(lines and "}\n") set ok to append_list(lines and "\n") @@ -4928,10 +4951,6 @@ define ep_rt_core_31: set ok to append_list(lines and " int n = recv((int)fd, buf + total, (int)(count - total), 0);\n") set ok to append_list(lines and " if (n <= 0) break;\n") set ok to append_list(lines and " total += n;\n") - return join_strings(lines) - -define ep_rt_core_32: - set lines to create_list() set ok to append_list(lines and " }\n") set ok to append_list(lines and "#else\n") set ok to append_list(lines and " ssize_t total = 0;\n") @@ -4956,7 +4975,6 @@ define ep_rt_core_32: set ok to append_list(lines and " }\n") set ok to append_list(lines and " return list_ptr;\n") set ok to append_list(lines and "}\n") - set ok to append_list(lines and "\n") return join_strings(lines) define ep_rt_builtins_0: diff --git a/epc.ep b/epc.ep index d726d77..f5e23ff 100644 --- a/epc.ep +++ b/epc.ep @@ -302,6 +302,10 @@ define main: return 1 if check_only == 1: + set safety_ok to validate_program_safety(program_ast) + if safety_ok == 0: + display "Compilation failed: ownership/safety errors." + return 1 display "Check passed: no errors." return 0 @@ -319,8 +323,11 @@ define main: set c_path to string_concat(stem and "_compiled.c") set ok to write_file_content(c_path and c_code) - display "[3/3] Compiling and Linking via Clang..." - set compile_cmd to "clang " + set c_compiler to "clang" + if ep_system("command -v clang >/dev/null 2>&1") != 0: + set c_compiler to "gcc" + display f"[3/3] Compiling and Linking via {c_compiler}..." + set compile_cmd to string_concat(c_compiler and " ") set compile_cmd to string_concat(compile_cmd and c_path) set compile_cmd to string_concat(compile_cmd and " -o ") set compile_cmd to string_concat(compile_cmd and stem) diff --git a/ernosplain-syntax/ernosplain.tmLanguage.json b/ernosplain-syntax/ernosplain.tmLanguage.json index 128faf9..606191e 100644 --- a/ernosplain-syntax/ernosplain.tmLanguage.json +++ b/ernosplain-syntax/ernosplain.tmLanguage.json @@ -1,75 +1,143 @@ { "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", "name": "ErnosPlain", + "scopeName": "source.ep", "patterns": [ - { - "include": "#comments" - }, - { - "include": "#strings" - }, - { - "include": "#keywords" - }, - { - "include": "#operators" - }, - { - "include": "#numbers" - } + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#declarations" }, + { "include": "#types" }, + { "include": "#constants" }, + { "include": "#keywords" }, + { "include": "#operators" }, + { "include": "#numbers" }, + { "include": "#punctuation" } ], "repository": { "comments": { "patterns": [ - { - "name": "comment.line.number-sign.ep", - "match": "#.*$" - } + { "name": "comment.line.documentation.ep", "match": "^\\s*###.*$" }, + { "name": "comment.line.number-sign.ep", "match": "#.*$" } ] }, "strings": { "patterns": [ { - "name": "string.quoted.double.ep", - "begin": "\"", - "end": "\"", + "name": "string.quoted.triple.ep", + "begin": "\\\"\\\"\\\"", + "end": "\\\"\\\"\\\"", + "patterns": [{ "include": "#escapes" }] + }, + { + "name": "string.interpolated.ep", + "begin": "\\bf\\s*(\\\")", + "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.ep" } }, + "end": "(\\\")", + "endCaptures": { "1": { "name": "punctuation.definition.string.end.ep" } }, "patterns": [ + { "include": "#escapes" }, { - "name": "constant.character.escape.ep", - "match": "\\\\." + "name": "meta.interpolation.ep", + "begin": "\\{", + "beginCaptures": { "0": { "name": "punctuation.section.interpolation.begin.ep" } }, + "end": "\\}", + "endCaptures": { "0": { "name": "punctuation.section.interpolation.end.ep" } }, + "patterns": [{ "include": "$self" }] } ] + }, + { + "name": "string.quoted.double.ep", + "begin": "\\\"", + "end": "\\\"", + "patterns": [{ "include": "#escapes" }] + } + ] + }, + "escapes": { + "patterns": [ + { "name": "constant.character.escape.ep", "match": "\\\\(?:[nt\\\"\\\\]|.)" } + ] + }, + "declarations": { + "patterns": [ + { + "match": "\\b(define|describe)\\s+(structure|choice|trait)\\s+([A-Za-z_][A-Za-z0-9_]*)", + "captures": { + "1": { "name": "keyword.declaration.ep" }, + "2": { "name": "storage.type.ep" }, + "3": { "name": "entity.name.type.ep" } + } + }, + { + "match": "\\b(define|describe)\\s+([A-Za-z_][A-Za-z0-9_]*)", + "captures": { + "1": { "name": "keyword.declaration.function.ep" }, + "2": { "name": "entity.name.function.ep" } + } + }, + { + "match": "\\b(variant|field)\\s+([A-Za-z_][A-Za-z0-9_]*)", + "captures": { + "1": { "name": "keyword.declaration.ep" }, + "2": { "name": "variable.other.member.ep" } + } + }, + { + "match": "\\b(import)\\s+(\\\")([^\\\"]+)(\\\")", + "captures": { + "1": { "name": "keyword.control.import.ep" }, + "2": { "name": "punctuation.definition.string.begin.ep" }, + "3": { "name": "string.quoted.double.ep" }, + "4": { "name": "punctuation.definition.string.end.ep" } + } } ] }, + "types": { + "patterns": [ + { "name": "support.type.builtin.ep", "match": "\\b(?:Int|Float|Bool|Str|List|Map|Channel|Unit)\\b" }, + { "name": "storage.type.ep", "match": "\\b(?:structure|choice|trait|variant|field|as|returning|returns|implement)\\b" } + ] + }, + "constants": { + "patterns": [ + { "name": "constant.language.boolean.ep", "match": "\\b(?:true|false)\\b" }, + { "name": "constant.language.channel.ep", "match": "\\bchannel\\b" } + ] + }, "keywords": { "patterns": [ { "name": "keyword.control.ep", - "match": "\\b(define|if|else|return|repeat|while|with|and|to|set|display)\\b" + "match": "\\b(?:if|else|return|give\\s+back|repeat|loop|while|for|each|every|in|range|break|stop|continue|skip|check|try|async|await|spawn)\\b" + }, + { + "name": "keyword.other.ep", + "match": "\\b(?:define|describe|with|and|set|let|to|be|display|show|print|import|send|receive|from|external|borrow|is|create|on|of|given)\\b" } ] }, "operators": { "patterns": [ { - "name": "keyword.operator.ep", - "match": "\\b(plus|minus|multiplied by|divided by|and also|or else|is less than|is greater than|is equal to|is not equal to|equals)\\b" + "name": "keyword.operator.word.ep", + "match": "\\b(?:multiplied\\s+by|divided\\s+by|and\\s+also|or\\s+else|is\\s+not\\s+equal\\s+to|is\\s+less\\s+than|is\\s+greater\\s+than|is\\s+equal\\s+to|is\\s+at\\s+least|is\\s+at\\s+most|is\\s+more\\s+than|is\\s+fewer\\s+than|is\\s+smaller\\s+than|is\\s+bigger\\s+than|is\\s+larger\\s+than|is\\s+the\\s+same\\s+as|is\\s+different\\s+from|does\\s+not\\s+equal|plus|minus|times|modulo|equals|not)\\b" }, - { - "name": "keyword.operator.symbol.ep", - "match": "(\\+|\\-|\\*|\\/|\\<|\\>|\\=|\\!|\\&{2}|\\|{2})" - } + { "name": "keyword.operator.symbol.ep", "match": "==|!=|<=|>=|&&|\\|\\||[+\\-*/%<>=]" } ] }, "numbers": { "patterns": [ - { - "name": "constant.numeric.ep", - "match": "\\b[0-9]+\\b" - } + { "name": "constant.numeric.float.ep", "match": "\\b[0-9]+\\.[0-9]+\\b" }, + { "name": "constant.numeric.integer.ep", "match": "\\b[0-9]+\\b" } + ] + }, + "punctuation": { + "patterns": [ + { "name": "punctuation.separator.ep", "match": "[,.:]" }, + { "name": "punctuation.section.brackets.ep", "match": "[()\\[\\]]" } ] } - }, - "scopeName": "source.ep" + } } diff --git a/ernosplain-syntax/language-configuration.json b/ernosplain-syntax/language-configuration.json index 92cc94a..54edb2b 100644 --- a/ernosplain-syntax/language-configuration.json +++ b/ernosplain-syntax/language-configuration.json @@ -1,22 +1,14 @@ { - "comments": { - "lineComment": "#" - }, - "brackets": [ - ["(", ")"] - ], + "comments": { "lineComment": "#" }, + "brackets": [["(", ")"], ["[", "]"]], "autoClosingPairs": [ - { - "open": "\"", - "close": "\"" - }, - { - "open": "(", - "close": ")" - } + { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, + { "open": "(", "close": ")" }, + { "open": "[", "close": "]" } ], - "surroundingPairs": [ - ["\"", "\""], - ["(", ")"] - ] + "surroundingPairs": [["\"", "\""], ["(", ")"], ["[", "]"]], + "indentationRules": { + "increaseIndentPattern": "^\\s*(?:async\\s+)?(?:define|describe|if|else(?:\\s+if)?|repeat\\s+while|while|for\\s+(?:each|every)|check|implement|given)\\b.*:\\s*(?:#.*)?$", + "decreaseIndentPattern": "^\\s*(?:else(?:\\s+if)?|if\\s+[A-Za-z_][A-Za-z0-9_]*\\s+with)\\b.*:\\s*(?:#.*)?$" + } } diff --git a/forensic/test_concurrency.expected b/forensic/test_concurrency.expected new file mode 100644 index 0000000..2e649dc --- /dev/null +++ b/forensic/test_concurrency.expected @@ -0,0 +1,4 @@ +Basic channel: 42 +Total from 2 producers (10 messages): 1520 +First from producer 3: 300 +CONCURRENCY: ALL PASSED diff --git a/forensic/test_english_syntax.expected b/forensic/test_english_syntax.expected new file mode 100644 index 0000000..f2c0d10 --- /dev/null +++ b/forensic/test_english_syntax.expected @@ -0,0 +1,17 @@ +plus=15 minus=7 times=20 div=5 mod=2 +plus=15 minus=7 times=20 div=5 mod=2 +5 < 10: correct +10 > 5: correct +5 == 5: correct +5 < 10: correct (op) +10 > 5: correct (op) +5 == 5: correct (op) +5 != 6: correct (op) +and also: correct +or else: correct +not: correct +&&: correct +||: correct +repeat while counter: 3 +while counter: 3 +ENGLISH ALIASES: ALL PASSED diff --git a/forensic/test_error_handling.expected b/forensic/test_error_handling.expected new file mode 100644 index 0000000..0104299 --- /dev/null +++ b/forensic/test_error_handling.expected @@ -0,0 +1,4 @@ +10/2 = 5 +Error: Division by zero +Try result: 20 +ERROR HANDLING: ALL PASSED diff --git a/forensic/test_float_ieee754.expected b/forensic/test_float_ieee754.expected new file mode 100644 index 0000000..3baa9c7 --- /dev/null +++ b/forensic/test_float_ieee754.expected @@ -0,0 +1,9 @@ +3.14159265358979 +4 +12 +3.33333333333333 +42 +99 +6.25 +-1.5 +FLOAT IEEE754: ALL PASSED diff --git a/forensic/test_hof_closures.expected b/forensic/test_hof_closures.expected new file mode 100644 index 0000000..ad78f57 --- /dev/null +++ b/forensic/test_hof_closures.expected @@ -0,0 +1,10 @@ +42 +31 +1 +4 +9 +16 +25 +Sum of 1..5: 15 +Scaled 5: 50 +HOF/CLOSURES: ALL PASSED diff --git a/forensic/test_ownership.expected b/forensic/test_ownership.expected new file mode 100644 index 0000000..adab7ba --- /dev/null +++ b/forensic/test_ownership.expected @@ -0,0 +1,7 @@ +Sum via borrow: 15 +Data still accessible after borrow +Point: 3, 4 +Origin still usable: 3 +Channel received: 42 +Spawn result: 100 +OWNERSHIP: ALL PASSED diff --git a/forensic/test_perf_fib.ep b/forensic/test_perf_fib.ep index 0171cb8..058f13b 100644 --- a/forensic/test_perf_fib.ep +++ b/forensic/test_perf_fib.ep @@ -10,6 +10,7 @@ define main: set result to fib(40) set end to ep_time_now_ms() set elapsed to end minus start - display f"fib(40) = {result}" - display f"Time: {elapsed}ms" + if result != 102334155 || elapsed < 0: + return 1 + display "PERF FIB: ALL PASSED" return 0 diff --git a/forensic/test_perf_fib.expected b/forensic/test_perf_fib.expected new file mode 100644 index 0000000..fe0f159 --- /dev/null +++ b/forensic/test_perf_fib.expected @@ -0,0 +1 @@ +PERF FIB: ALL PASSED diff --git a/forensic/test_stdlib.ep b/forensic/test_stdlib.ep index 8dc08e6..837aeef 100644 --- a/forensic/test_stdlib.ep +++ b/forensic/test_stdlib.ep @@ -35,15 +35,18 @@ define main: # 4. Random set rand to ep_random_int(0 and 100) - display f"Random 0-100: {rand}" + if rand < 0 || rand > 100: + return 1 # 5. Time set now to ep_time_now_ms() - display f"Current time (ms): {now}" + if now < 1: + return 1 # 6. UUID set id to uuid_v4() - display f"UUID: {id}" + if string_length(id) != 36: + return 1 display "STDLIB: ALL PASSED" return 0 diff --git a/forensic/test_stdlib.expected b/forensic/test_stdlib.expected new file mode 100644 index 0000000..cd25abc --- /dev/null +++ b/forensic/test_stdlib.expected @@ -0,0 +1,12 @@ +HELLO, WORLD! +hello, world! +Trimmed: 'spaces' +Contains World: 1 +Hello, ErnosPlain! +Index of World: 7 +a +b +c +d +abs(-42): 42 +STDLIB: ALL PASSED diff --git a/forensic/test_structs_enums_traits.expected b/forensic/test_structs_enums_traits.expected new file mode 100644 index 0000000..a91ca72 --- /dev/null +++ b/forensic/test_structs_enums_traits.expected @@ -0,0 +1,8 @@ +Dog has 4 legs +Dog legs: 4 +Circle area: 75 +Rectangle area: 12 +I am Cat +Trait returned: 4 +It's a circle with radius 10 +STRUCTS/ENUMS/TRAITS: ALL PASSED diff --git a/forensic/test_type_safety.expected b/forensic/test_type_safety.expected new file mode 100644 index 0000000..e4731dc --- /dev/null +++ b/forensic/test_type_safety.expected @@ -0,0 +1,12 @@ +52 +200 +7 +flag is true +ErnosPlain +10 +20 +30 +21 +25 +Age is 25, doubled is 50 +TYPE SYSTEM: ALL PASSED diff --git a/install.sh b/install.sh index f4a2843..2aa5256 100755 --- a/install.sh +++ b/install.sh @@ -60,7 +60,7 @@ elif [ "$OS" = "Darwin" ]; then fi if ! command -v cargo &> /dev/null; then - echo -e "${RED}Error: Rust/Cargo is required to build the bootstrap compiler driver.${NC}" + echo -e "${RED}Error: Rust/Cargo is required to build the reference compiler.${NC}" echo -e "${RED}Please install Rust from https://rustup.rs/ before running this installer.${NC}" exit 1 fi @@ -70,27 +70,30 @@ echo "" # 3. Compilation Phase echo -e "${BOLD}3. Compiling ErnosPlain from source...${NC}" -# A. Build the Rust bootstrap compiler +# A. Build the Rust reference compiler echo "Building the Rust bootstrap compiler driver..." cargo build --release --quiet -cp target/release/ernos ./epc_bootstrap -# B. Concatenate and build the self-hosted compiler -echo "Generating the self-hosted compiler unit..." -# Strip the import lines from epc.ep — those modules are already prepended by cat. -# Without this, the self-hosted compiler sees double definitions during self-replication. -cat ep_lexer.ep ep_parser.ep ep_codegen.ep <(grep -v '^import "ep_' epc.ep) > self_hosted_compiler.ep - -echo "Compiling self-hosted compiler with the bootstrap compiler..." -./epc_bootstrap self_hosted_compiler.ep - -# C. Verify self-replication -echo "Replicating compiler to second-generation binary..." -cp ./self_hosted_compiler ./self_hosted_compiler_gen1 -./self_hosted_compiler_gen1 self_hosted_compiler.ep +# B. Build the modular self-hosted compiler. The reference driver resolves all +# imports in epc.ep, including checker, optimizer, and generated runtime modules. +echo "Compiling the modular self-hosted compiler..." +./target/release/ernos epc.ep + +# C. Verify a stable three-generation self-replication fixpoint. +echo "Replicating compiler to second generation..." +cp ./epc ./epc_gen1 +./epc_gen1 epc.ep + +echo "Replicating compiler to third generation..." +cp ./epc ./epc_gen2 +./epc_gen2 epc.ep +if ! cmp -s ./epc_gen2 ./epc; then + echo -e "${RED}Error: self-hosted compiler did not reach a gen2/gen3 fixpoint.${NC}" + exit 1 +fi # D. Clean up intermediate build products -rm -f ./epc_bootstrap ./self_hosted_compiler_gen1 ./self_hosted_compiler.ep +rm -f ./epc_gen1 ./epc_gen2 echo -e "${GREEN}✓ Compilation and self-replication successful!${NC}" echo "" @@ -100,7 +103,7 @@ echo -e "${BOLD}4. Installing binaries...${NC}" INSTALL_DIR="$HOME/.local/bin" mkdir -p "$INSTALL_DIR" -mv ./self_hosted_compiler "$INSTALL_DIR/epc" +mv ./epc "$INSTALL_DIR/epc" echo -e "${GREEN}✓ Installed 'epc' (self-hosted compiler) to $INSTALL_DIR/epc${NC}" # Install the feature-complete driver too. `ernos` provides check/transpile/bind/ @@ -113,6 +116,11 @@ echo -e "${GREEN}✓ Installed 'ernos' (full CLI) to $INSTALL_DIR/ernos${NC}" rm -rf "$INSTALL_DIR/stdlib" cp -R stdlib "$INSTALL_DIR/stdlib" echo -e "${GREEN}✓ Installed standard library to $INSTALL_DIR/stdlib${NC}" + +# Verify the installed binaries themselves, not only their build-tree copies. +"$INSTALL_DIR/epc" check tests/test_native_basic.ep >/dev/null +"$INSTALL_DIR/ernos" check tests/test_native_basic.ep >/dev/null +echo -e "${GREEN}✓ Installed compilers passed static validation smoke tests.${NC}" echo "" # 5. PATH Verification and Guide diff --git a/run_tests.sh b/run_tests.sh index 852ef16..069530f 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -23,7 +23,7 @@ FAIL=0 SKIP=0 FAILURES="" -for TEST_FILE in tests/test_*.ep conformance/test_*.ep; do +for TEST_FILE in tests/test_*.ep conformance/test_*.ep forensic/test_*.ep; do [[ -f "$TEST_FILE" ]] || continue NAME=$(basename "$TEST_FILE" .ep) BINARY="$(dirname "$TEST_FILE")/$NAME" diff --git a/runtime/ep_runtime.c b/runtime/ep_runtime.c index b88d396..07e77a2 100644 --- a/runtime/ep_runtime.c +++ b/runtime/ep_runtime.c @@ -37,6 +37,7 @@ typedef int pthread_attr_t; #define pthread_detach(t) ((void)(t), 0) #else #include +#include #endif #include #include @@ -1796,6 +1797,7 @@ long long json_get_int(long long json_val, long long key_val); long long json_get_bool(long long json_val, long long key_val); long long ep_sha1(long long data_val); long long ep_net_recv_bytes(long long fd, long long count); +long long ep_net_send_raw(long long fd, long long data_ptr, long long count); long long channel_try_recv(long long chan_ptr, long long out_ptr); long long channel_has_data(long long chan_ptr); long long channel_select(long long channels_list, long long timeout_ms); @@ -2068,6 +2070,11 @@ long long ep_net_send(long long fd, const char* data) { return 0; } +long long ep_net_send_raw(long long fd, long long data_ptr, long long count) { + (void)fd; (void)data_ptr; (void)count; + return 0; +} + char* ep_net_recv(long long fd, long long max_len) { (void)fd; (void)max_len; char* empty = malloc(1); @@ -2206,17 +2213,29 @@ long long ep_net_accept(long long server_fd) { long long ep_net_send(long long fd, const char* data) { if (!data) return 0; - /* send() may write fewer bytes than requested (partial write under load/ - backpressure). A single send() therefore silently truncated large IPC - responses, cutting agent replies mid-stream. Loop until all bytes are sent. */ - size_t total = strlen(data); - size_t off = 0; - while (off < total) { - ssize_t n = send((int)fd, data + off, total - off, 0); + return ep_net_send_raw(fd, (long long)data, (long long)strlen(data)); +} + +long long ep_net_send_raw(long long fd, long long data_ptr, long long count) { + if (data_ptr == 0 || count <= 0) return 0; + + /* send() may write fewer bytes than requested. Keep sending until the + explicit byte count is exhausted, including bytes after embedded NULs. */ + const char* data = (const char*)data_ptr; + long long off = 0; + while (off < count) { +#ifdef _WIN32 + int chunk = count - off > INT_MAX ? INT_MAX : (int)(count - off); + int n = send((int)fd, data + off, chunk, 0); + if (n < 0 && WSAGetLastError() == WSAEINTR) continue; +#else + ssize_t n = send((int)fd, data + off, (size_t)(count - off), 0); + if (n < 0 && errno == EINTR) continue; +#endif if (n <= 0) break; - off += (size_t)n; + off += (long long)n; } - return (long long)off; + return off; } char* ep_net_recv(long long fd, long long max_len) { @@ -4822,4 +4841,3 @@ long long ep_get_args(void) { } return list_ptr; } - diff --git a/spec/ernos-spec.md b/spec/ernos-spec.md index 93f3cf0..07458cf 100644 --- a/spec/ernos-spec.md +++ b/spec/ernos-spec.md @@ -46,7 +46,7 @@ ErnosPlain uses indentation (4 spaces) to delimit blocks, similar to Python. | Type | Description | C Representation | |------|-------------|-----------------| | `Int` | 64-bit signed integer | `long long` | -| `Float` | 64-bit IEEE 754 double | `double` (partially supported in codegen) | +| `Float` | 64-bit IEEE 754 double | Bit-preserving `double` representation with arithmetic and FFI support | | `Bool` | Boolean value | `long long` (0 or 1) | | `Str` | Static string (immutable) | `const char*` cast to `long long` | | `DynStr` | Dynamic string (heap) | `char*` (malloc'd) cast to `long long` | diff --git a/src/codegen.rs b/src/codegen.rs index 867aa81..90bcc85 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -426,6 +426,7 @@ impl Codegen { self.func_return_types.insert("ep_net_listen".to_string(), Type::Int); self.func_return_types.insert("ep_net_accept".to_string(), Type::Int); self.func_return_types.insert("ep_net_send".to_string(), Type::Int); + self.func_return_types.insert("ep_net_send_raw".to_string(), Type::Int); self.func_return_types.insert("ep_net_recv".to_string(), Type::DynStr); self.func_return_types.insert("ep_net_close".to_string(), Type::Int); self.func_return_types.insert("append_list".to_string(), Type::Int); @@ -2559,6 +2560,20 @@ impl Codegen { // ========== analyze_safety, generate, gen_function ========== impl Codegen { + /// Run the code generator's ownership/lifetime analysis without emitting C. + /// This keeps `check` and non-C backends aligned with the safety guarantees + /// enforced by the normal C generation path. + pub fn validate_safety(&mut self, program: &Program) -> Result<(), String> { + for sd in &program.struct_defs { + self.struct_defs.insert(sd.name.clone(), sd.clone()); + } + for ed in &program.enum_defs { + self.enum_defs.insert(ed.name.clone(), ed.clone()); + } + self.analyze_return_types(program); + self.analyze_safety(program) + } + fn analyze_safety(&mut self, program: &Program) -> Result<(), String> { for func in &program.functions { let mut var_types = HashMap::new(); diff --git a/src/main.rs b/src/main.rs index 526c6f9..a1ceb1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -938,13 +938,15 @@ fn main() { println!(" ep_net_listen(port: Int) -> Int TCP listen"); println!(" ep_net_accept(fd: Int) -> Int TCP accept"); println!(" ep_net_send(fd: Int, data: Str) -> Int Send data"); + println!(" ep_net_send_raw(fd: Int, ptr: Int, len: Int) -> Int Send exact bytes"); println!(" ep_net_recv(fd: Int, max: Int) -> Str Receive data"); + println!(" ep_net_recv_bytes(fd: Int, len: Int) -> Str Receive exact bytes"); println!(" ep_net_close(fd: Int) -> Int Close connection"); println!(); return; } - // Handle --check (syntax check only, no codegen) + // Handle --check (full static validation, no code generation) if args[1] == "--check" || args[1] == "check" { if args.len() < 3 { eprintln!("Usage: epc --check "); @@ -991,6 +993,24 @@ fn main() { std::process::exit(1); } + let borrow_errors = borrow_check::BorrowChecker::check(&program); + if !borrow_errors.is_empty() { + eprintln!("\n\x1b[1;31m── Ownership Errors ({}) ──\x1b[0m", borrow_errors.len()); + for err in &borrow_errors { + eprint!("{}", err); + } + eprintln!(); + eprintln!("\x1b[1;31m✗\x1b[0m {} — {} ownership/borrowing error(s) found", + args[2], borrow_errors.len()); + std::process::exit(1); + } + + let mut safety_codegen = codegen::Codegen::new(); + if let Err(err) = safety_codegen.validate_safety(&program) { + eprintln!("\x1b[1;31m✗\x1b[0m {} — safety error: {}", args[2], err); + std::process::exit(1); + } + println!("\x1b[1;32m✓\x1b[0m {} — no errors ({} functions, {} structs, {} enums)", args[2], all_functions.len(), all_struct_defs.len(), all_enum_defs.len()); } @@ -1107,6 +1127,43 @@ fn main() { let use_llvm = !use_wasm && (args.iter().any(|a| a == "--llvm") || (llc_available && !args.iter().any(|a| a == "--native"))); let use_native = !use_wasm && args.iter().any(|a| a == "--native"); + // Every backend must pass the same front-end validation. In particular, + // native assembly must never become an escape hatch around type or + // ownership checks. + let (type_errors, _type_warnings) = type_check::TypeChecker::check_full(&program); + if !type_errors.is_empty() { + eprintln!("\n\x1b[1;31m── Type Errors ({}) ──\x1b[0m", type_errors.len()); + for err in &type_errors { + eprintln!(" \x1b[1;31merror\x1b[0m: {}", err); + } + eprintln!(); + eprintln!("\x1b[1;31mCompilation failed:\x1b[0m {} type error(s) found. Fix all type errors before compiling.", type_errors.len()); + std::process::exit(1); + } + + let borrow_errors = borrow_check::BorrowChecker::check(&program); + if !borrow_errors.is_empty() { + eprintln!("\n\x1b[1;31m── Ownership Errors ({}) ──\x1b[0m", borrow_errors.len()); + for err in &borrow_errors { + eprint!("{}", err); + } + eprintln!(); + eprintln!("\x1b[1;31mCompilation failed:\x1b[0m {} ownership/borrowing error(s) found. Fix all safety violations before compiling.", borrow_errors.len()); + std::process::exit(1); + } + + let mut safety_codegen = codegen::Codegen::new(); + if let Err(err) = safety_codegen.validate_safety(&program) { + eprintln!("Code Generation Safety Error: {}", err); + std::process::exit(1); + } + + let opt_stats = optimizer::Optimizer::run(&mut program); + if opt_stats.constants_folded > 0 || opt_stats.dead_stmts_eliminated > 0 { + eprintln!("\x1b[2m optimizer: {} constants folded, {} dead statements eliminated\x1b[0m", + opt_stats.constants_folded, opt_stats.dead_stmts_eliminated); + } + if use_native { let arch = std::env::consts::ARCH; let os = std::env::consts::OS; @@ -1212,7 +1269,7 @@ fn main() { .status() } else { // Linux: use gcc to link, include pthread and math libs - Command::new("gcc") + Command::new(cc) .arg("-no-pie") .arg("-o").arg(&stem) .arg(&obj_path) @@ -1244,36 +1301,6 @@ fn main() { return; } - // Type checking (Phase 1A) — HARD ERRORS: reject programs with type errors - let (type_errors, _type_warnings) = type_check::TypeChecker::check_full(&program); - if !type_errors.is_empty() { - eprintln!("\n\x1b[1;31m── Type Errors ({}) ──\x1b[0m", type_errors.len()); - for err in &type_errors { - eprintln!(" \x1b[1;31merror\x1b[0m: {}", err); - } - eprintln!(); - eprintln!("\x1b[1;31mCompilation failed:\x1b[0m {} type error(s) found. Fix all type errors before compiling.", type_errors.len()); - std::process::exit(1); - } - - // Borrow checking (Phase 3) — HARD ERRORS: reject programs with ownership violations - let borrow_errors = borrow_check::BorrowChecker::check(&program); - if !borrow_errors.is_empty() { - eprintln!("\n\x1b[1;31m── Ownership Errors ({}) ──\x1b[0m", borrow_errors.len()); - for err in &borrow_errors { - eprint!("{}", err); - } - eprintln!(); - eprintln!("\x1b[1;31mCompilation failed:\x1b[0m {} ownership/borrowing error(s) found. Fix all safety violations before compiling.", borrow_errors.len()); - std::process::exit(1); - } - // Optimization pass (Phase 4B) - let opt_stats = optimizer::Optimizer::run(&mut program); - if opt_stats.constants_folded > 0 || opt_stats.dead_stmts_eliminated > 0 { - eprintln!("\x1b[2m optimizer: {} constants folded, {} dead statements eliminated\x1b[0m", - opt_stats.constants_folded, opt_stats.dead_stmts_eliminated); - } - let output_executable = if use_wasm { if stem.ends_with(".wasm") { stem.clone() @@ -1376,9 +1403,14 @@ fn main() { std::process::exit(1); } - println!("[3/3] Compiling and Linking via Clang..."); + let c_compiler = if use_wasm || Command::new("clang").arg("--version").output().is_ok() { + "clang" + } else { + "gcc" + }; + println!("[3/3] Compiling and Linking via {}...", c_compiler); - let mut clang_cmd = Command::new("clang"); + let mut clang_cmd = Command::new(c_compiler); clang_cmd.arg(&c_path_str) .arg("-o") .arg(&output_executable); @@ -1764,14 +1796,14 @@ fn print_usage() { eprintln!(); eprintln!("\x1b[1mUSAGE:\x1b[0m"); eprintln!(" epc Compile to native binary"); - eprintln!(" epc --native Compile via native assembly (no Clang required)"); + eprintln!(" epc --native Native assembly frontend + C runtime (C compiler required)"); eprintln!(" epc --llvm Compile via LLVM IR backend (.ll)"); eprintln!(" epc --release Compile with optimizations (O3+LTO)"); eprintln!(" epc test Run as test"); eprintln!(); eprintln!("\x1b[1mDEV TOOLS:\x1b[0m"); eprintln!(" epc doc [-o dir] Generate markdown documentation"); - eprintln!(" epc --check Syntax check (no compilation)"); + eprintln!(" epc --check Full static validation (no code generation)"); eprintln!(" epc --format Auto-format source file"); eprintln!(" epc --list-builtins List all built-in functions"); eprintln!(" epc --version Show version info"); diff --git a/src/type_check.rs b/src/type_check.rs index 7744a59..3502ee3 100644 --- a/src/type_check.rs +++ b/src/type_check.rs @@ -1012,9 +1012,10 @@ impl TypeChecker { self.func_types.insert("ep_net_listen".into(), (vec![MonoType::Int], MonoType::Int)); self.func_types.insert("ep_net_accept".into(), (vec![MonoType::Int], MonoType::Int)); self.func_types.insert("ep_net_send".into(), (vec![MonoType::Int, MonoType::Str], MonoType::Int)); + self.func_types.insert("ep_net_send_raw".into(), (vec![MonoType::Int, MonoType::Int, MonoType::Int], MonoType::Int)); self.func_types.insert("ep_net_recv".into(), (vec![MonoType::Int, MonoType::Int], MonoType::DynStr)); self.func_types.insert("ep_net_recv_bytes".into(), (vec![MonoType::Int, MonoType::Int], MonoType::DynStr)); - self.func_types.insert("ep_net_close".into(), (vec![MonoType::Int], MonoType::Unit)); + self.func_types.insert("ep_net_close".into(), (vec![MonoType::Int], MonoType::Int)); // HTTP self.func_types.insert("ep_http_request".into(), (vec![MonoType::Str, MonoType::Str, MonoType::Str, MonoType::Str], MonoType::DynStr)); diff --git a/stdlib/net.ep b/stdlib/net.ep index dfaadcc..375f86c 100644 --- a/stdlib/net.ep +++ b/stdlib/net.ep @@ -1,19 +1,19 @@ # ErnosPlain Networking Standard Library -define net_connect with host and port: +define net_connect with host as Str and port as Int returning Int: return ep_net_connect(host and port) -define net_listen with port: +define net_listen with port as Int returning Int: return ep_net_listen(port) -define net_accept with server_fd: +define net_accept with server_fd as Int returning Int: return ep_net_accept(server_fd) -define net_send with fd and data: +define net_send with fd as Int and data as Str returning Int: return ep_net_send(fd and data) -define net_recv with fd and max_len: +define net_recv with fd as Int and max_len as Int returning Str: return ep_net_recv(fd and max_len) -define net_close with fd: +define net_close with fd as Int returning Int: return ep_net_close(fd) diff --git a/tests/run_check_gate.sh b/tests/run_check_gate.sh new file mode 100755 index 0000000..c73c958 --- /dev/null +++ b/tests/run_check_gate.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Check-only mode must perform full semantic and ownership validation. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +RUST=./target/release/ernos +EPC=./epc +VALID=tests/test_native_basic.ep +INVALID=forensic/test_safety_move.ep + +[[ -x "$RUST" ]] || { echo "build the release compiler first" >&2; exit 2; } +[[ -x "$EPC" ]] || { echo "build the self-hosted compiler first" >&2; exit 2; } + +"$RUST" check "$VALID" >/dev/null +"$EPC" check "$VALID" >/dev/null + +if "$RUST" check "$INVALID" >/dev/null 2>&1; then + echo "ernos check accepted a use-after-move" >&2 + exit 1 +fi +if "$EPC" check "$INVALID" >/dev/null 2>&1; then + echo "epc check accepted a use-after-move" >&2 + exit 1 +fi + +echo "check gate: PASS (both compilers validate ownership without codegen)" diff --git a/tests/run_native_gate.sh b/tests/run_native_gate.sh new file mode 100755 index 0000000..59c90a1 --- /dev/null +++ b/tests/run_native_gate.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Native backend smoke + safety gate. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +COMPILER=./target/release/ernos +VALID=tests/test_native_basic.ep +INVALID=tests/test_arg_type_mismatch.ep +OWNERSHIP_INVALID=forensic/test_safety_move.ep +BIN=tests/test_native_basic +trap 'rm -f "$BIN" tests/test_native_basic_native.s tests/test_native_basic_native.o tests/test_native_basic_runtime.c tests/test_native_basic_runtime.o' EXIT + +[[ -x "$COMPILER" ]] || { echo "build the release compiler first" >&2; exit 2; } + +"$COMPILER" "$VALID" --native >/tmp/ernos_native_compile.log 2>&1 +actual=$("$BIN") +expected=$(cat tests/test_native_basic.expected) +[[ "$actual" == "$expected" ]] || { + echo "native backend output mismatch" >&2 + diff <(printf '%s\n' "$expected") <(printf '%s\n' "$actual") + exit 1 +} + +if "$COMPILER" "$INVALID" --native >/tmp/ernos_native_reject.log 2>&1; then + echo "native backend accepted an invalid typed program" >&2 + exit 1 +fi +if "$COMPILER" "$OWNERSHIP_INVALID" --native >/tmp/ernos_native_ownership_reject.log 2>&1; then + echo "native backend accepted a use-after-move" >&2 + exit 1 +fi + +echo "native gate: PASS (valid program runs; type and ownership errors rejected)" diff --git a/tests/run_stdlib_gate.sh b/tests/run_stdlib_gate.sh index 2799c7a..5d9c3f3 100755 --- a/tests/run_stdlib_gate.sh +++ b/tests/run_stdlib_gate.sh @@ -16,9 +16,8 @@ # across stdlib/*.ep. # Part 2 One program importing every linkable stdlib module is compiled by # BOTH compilers, run, and the outputs must match exactly. -# Part 3 Modules excluded from Part 2 (external C library deps or known -# runtime gaps) still get parse + typecheck coverage via -# `ernos --check`, which resolves and checks all imports. +# Part 3 Modules excluded from Part 2 for external C library dependencies +# still get full check-only coverage from both compilers. # # Exit code: 0 only if every part passes. set -uo pipefail @@ -72,10 +71,6 @@ link_modules="" for f in stdlib/*.ep; do m=$(basename "$f" .ep) case "$m" in - websocket) - # websocket.ep calls the external ep_net_send_raw, which the C runtime - # does not provide yet — importing it always fails at link time. - skip_link="$skip_link $m(missing-runtime-ep_net_send_raw)"; continue ;; gui) probe_lib -lraylib || { skip_link="$skip_link $m(no-raylib)"; continue; } ;; sql) @@ -121,9 +116,8 @@ if [ $fail -eq 0 ]; then fi # ───────────────── Part 3: --check coverage for link-excluded modules ───────────────── -# `ernos --check` resolves imports and runs parse + typecheck without -# invoking clang, so grammar drift in these modules is still caught. -# (epc has no --check mode, so this leg is Rust-only.) +# Both check modes resolve imports and perform semantic + ownership checks +# without invoking clang, so grammar or safety drift is still caught. skipped_names=$(echo "$skip_link" | tr ' ' '\n' | sed 's/(.*//' | grep -v '^$' || true) if [ -n "$skipped_names" ]; then { @@ -131,12 +125,19 @@ if [ -n "$skipped_names" ]; then printf '\ndefine main:\n display "ok"\n' } > "$ROOT/${STEM}_check.ep" if ./target/release/ernos --check "${STEM}_check.ep" > "$WORK/check.log" 2>&1; then - echo "check-only parse+typecheck ok:$(echo " $skipped_names" | tr '\n' ' ')" + echo "check-only ernos ok:$(echo " $skipped_names" | tr '\n' ' ')" else echo "CHECK-FAIL: a link-excluded module no longer parses/typechecks:" tail -15 "$WORK/check.log" | sed 's/^/ /' fail=1 fi + if ./epc check "${STEM}_check.ep" > "$WORK/epc_check.log" 2>&1; then + echo "check-only epc ok:$(echo " $skipped_names" | tr '\n' ' ')" + else + echo "EPC-CHECK-FAIL: a link-excluded module no longer passes static validation:" + tail -15 "$WORK/epc_check.log" | sed 's/^/ /' + fail=1 + fi fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/tests/test_concurrency.expected b/tests/test_concurrency.expected new file mode 100644 index 0000000..7b2c74d --- /dev/null +++ b/tests/test_concurrency.expected @@ -0,0 +1,3 @@ +=== Concurrency === +total from 3 workers: 60 +ALL CONCURRENCY TESTS PASSED diff --git a/tests/test_dlopen.ep b/tests/test_dlopen.ep index 7c8076b..40300a0 100644 --- a/tests/test_dlopen.ep +++ b/tests/test_dlopen.ep @@ -2,20 +2,20 @@ define main: # Test ep_dlopen with libm (math library) set lib to ep_dlopen("libm.dylib") if lib equals 0: - # macOS: try alternate path set lib to ep_dlopen("/usr/lib/libm.dylib") + if lib equals 0: + set lib to ep_dlopen("libm.so.6") + if lib equals 0: + set lib to ep_dlopen("libm.so") if lib equals 0: - display "dlopen: could not load libm (expected on some systems)" - display "PASS: dlopen API is callable" - else: - display "dlopen: loaded libm" - set abs_fn to ep_dlsym(lib and "abs") - if abs_fn equals 0: - display "dlsym: abs not found (expected - long long vs int)" - else: - display "dlsym: found abs" - set result to ep_dlclose(lib) - display "dlclose: done" - display "PASS: dlopen/dlsym/dlclose work" + return 1 + set cos_fn to ep_dlsym(lib and "cos") + if cos_fn equals 0: + ep_dlclose(lib) + return 1 + set result to ep_dlclose(lib) + if result != 0: + return 1 + display "PASS: dlopen/dlsym/dlclose work" return 0 diff --git a/tests/test_dlopen.expected b/tests/test_dlopen.expected new file mode 100644 index 0000000..f1a9573 --- /dev/null +++ b/tests/test_dlopen.expected @@ -0,0 +1 @@ +PASS: dlopen/dlsym/dlclose work diff --git a/tests/test_doc_generator.expected b/tests/test_doc_generator.expected new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_fileio.expected b/tests/test_fileio.expected new file mode 100644 index 0000000..19dca56 --- /dev/null +++ b/tests/test_fileio.expected @@ -0,0 +1,6 @@ +=== File I/O === +exists after write: 1 +read: hello from ernos +after append: hello from ernos +second line +ALL FILE TESTS PASSED diff --git a/tests/test_float_ffi.expected b/tests/test_float_ffi.expected new file mode 100644 index 0000000..6ce8543 --- /dev/null +++ b/tests/test_float_ffi.expected @@ -0,0 +1,13 @@ +sin(0.0) = +0 +cos(0.0) = +1 +sqrt(4.0) = +2 +sqrt(9.0) = +3 +pow(2.0, 10.0) = +1024 +fabs(-42.5) = +42.5 +Float FFI test complete! diff --git a/tests/test_math.ep b/tests/test_math.ep index 986249d..1f606db 100644 --- a/tests/test_math.ep +++ b/tests/test_math.ep @@ -1,30 +1,26 @@ # Math and Utility Test — Full Coverage define main: - display "=== Random ===" set r to ep_random_int(0 and 100) - display f"random (0-100): {r}" + if r < 0 || r > 100: + return 1 - display "=== Abs ===" - display f"abs(-42): {ep_abs(-42)}" - display f"abs(42): {ep_abs(42)}" - display f"abs(0): {ep_abs(0)}" + if ep_abs(-42) != 42 || ep_abs(42) != 42 || ep_abs(0) != 0: + return 1 - display "=== Time ===" set t1 to ep_time_now_ms() ep_sleep_ms(50) set t2 to ep_time_now_ms() set diff to t2 - t1 - display f"sleep 50ms, elapsed: {diff}ms" - if diff >= 40: - display "timing ok" + if diff < 40: + return 1 - display "=== Time Components ===" set now to ep_time_now_sec() set year to ep_time_year(now) set month to ep_time_month(now) set day to ep_time_day(now) - display f"date: {year}-{month}-{day}" + if year < 2020 || month < 1 || month > 12 || day < 1 || day > 31: + return 1 - display "ALL MATH TESTS PASSED" + display "PASS: math, random, and time utilities" return 0 diff --git a/tests/test_math.expected b/tests/test_math.expected new file mode 100644 index 0000000..dfc828a --- /dev/null +++ b/tests/test_math.expected @@ -0,0 +1 @@ +PASS: math, random, and time utilities diff --git a/tests/test_net_send_raw.ep b/tests/test_net_send_raw.ep new file mode 100644 index 0000000..3411a75 --- /dev/null +++ b/tests/test_net_send_raw.ep @@ -0,0 +1,57 @@ +external define ep_net_send_raw with fd and buf and count: +external define ep_net_recv_bytes with fd and count: + +define send_binary_payload with port: + set fd to ep_net_connect("127.0.0.1" and port) + if fd < 0: + return 1 + set buf to alloc_bytes(5) + poke_byte(buf and 0 and 65) + poke_byte(buf and 1 and 0) + poke_byte(buf and 2 and 66) + poke_byte(buf and 3 and 255) + poke_byte(buf and 4 and 67) + set sent to ep_net_send_raw(fd and buf and 5) + free_bytes(buf) + ep_net_close(fd) + if sent != 5: + return 1 + return 0 + +define main: + set port to 39091 + set server to 0 - 1 + repeat while server < 0 and also port < 39111: + set server to ep_net_listen(port) + if server < 0: + set port to port + 1 + if server < 0: + display "raw send test could not open listener" + return 1 + + spawn send_binary_payload(port) + set client to ep_net_accept(server) + if client < 0: + ep_net_close(server) + return 1 + + set data to ep_net_recv_bytes(client and 5) + set ptr to str_to_ptr(data) + set valid to 1 + if peek_byte(ptr and 0) != 65: + set valid to 0 + if peek_byte(ptr and 1) != 0: + set valid to 0 + if peek_byte(ptr and 2) != 66: + set valid to 0 + if peek_byte(ptr and 3) != 255: + set valid to 0 + if peek_byte(ptr and 4) != 67: + set valid to 0 + + ep_net_close(client) + ep_net_close(server) + if valid == 0: + return 1 + display "PASS: raw socket send preserves embedded null bytes" + return 0 diff --git a/tests/test_net_send_raw.expected b/tests/test_net_send_raw.expected new file mode 100644 index 0000000..c3f046e --- /dev/null +++ b/tests/test_net_send_raw.expected @@ -0,0 +1 @@ +PASS: raw socket send preserves embedded null bytes diff --git a/tests/test_package_installer.ep b/tests/test_package_installer.ep index 33c0496..cb58d09 100644 --- a/tests/test_package_installer.ep +++ b/tests/test_package_installer.ep @@ -1,8 +1,6 @@ # End-to-end integration test for the Package Registry and Installer define main: - display "=== Testing Package Manager ===" - # 1. Clean up old test folders set _ to ep_system("rm -rf test_dep_pkg test_app ~/.ernos_registry/test_dep_pkg ernos_modules/test_dep_pkg") @@ -10,7 +8,9 @@ define main: set _ to ep_system("mkdir -p test_dep_pkg") # 3. Initialize dependency package using init subcommand - set _ to ep_system("cd test_dep_pkg && ../target/debug/ernos package init") + set status to ep_system("cd test_dep_pkg && ../target/release/ernos package init >/dev/null") + if status != 0: + return 1 # Update manifest to set name to test_dep_pkg set manifest to "[package]\nname = \"test_dep_pkg\"\nversion = \"0.1.0\"\ndescription = \"dependency test package\"\n\n[dependencies]\n" @@ -21,35 +21,46 @@ define main: set _ to file_write("test_dep_pkg/src/lib.ep" and lib_src) # 4. Publish the dependency package to local registry - set _ to ep_system("cd test_dep_pkg && ../target/debug/ernos package publish") + set status to ep_system("cd test_dep_pkg && ../target/release/ernos package publish >/dev/null") + if status != 0: + return 1 # 5. Create client application set _ to ep_system("mkdir -p test_app") - set _ to ep_system("cd test_app && ../target/debug/ernos package init") + set status to ep_system("cd test_app && ../target/release/ernos package init >/dev/null") + if status != 0: + return 1 # Update app manifest to declare dependency on test_dep_pkg set app_manifest to "[package]\nname = \"test_app\"\nversion = \"0.1.0\"\n\n[dependencies]\ntest_dep_pkg = \"0.1.0\"\n" set _ to file_write("test_app/ernos.toml" and app_manifest) # 6. Install dependencies - set _ to ep_system("cd test_app && ../target/debug/ernos package install") + set status to ep_system("cd test_app && ../target/release/ernos package install >/dev/null") + if status != 0: + return 1 # Verify dependency file is copied under ernos_modules/ set exists to file_exists("test_app/ernos_modules/test_dep_pkg/src/lib.ep") - display f"Dependency file exists: {exists}" + if exists != 1: + return 1 # 7. Write main.ep using the dependency set main_src to "import \"test_dep_pkg\" as dep\n\ndefine main:\n display dep_my_dependency_func()\n return 0\n" set _ to file_write("test_app/main.ep" and main_src) # Compile the app - set compile_out to run_command("./target/debug/ernos test_app/main.ep") + set status to ep_system("./target/release/ernos test_app/main.ep >/dev/null") + if status != 0: + return 1 - # Run the compiled binary - set run_out to run_command("./test_app/main") - display f"App stdout: {run_out}" + # Run the compiled binary and verify its output. + set status to ep_system("./test_app/main > test_app/app.out") + set run_out to file_read("test_app/app.out") + if status != 0 || string_contains(run_out and "Hello from dependency!") == 0: + return 1 # 8. Clean up set _ to ep_system("rm -rf test_dep_pkg test_app ~/.ernos_registry/test_dep_pkg") - + display "PASS: package init, publish, install, import, and run" return 0 diff --git a/tests/test_package_installer.expected b/tests/test_package_installer.expected new file mode 100644 index 0000000..79dbb9e --- /dev/null +++ b/tests/test_package_installer.expected @@ -0,0 +1 @@ +PASS: package init, publish, install, import, and run diff --git a/tests/test_task_group.ep b/tests/test_task_group.ep index 3215c16..02bb7c2 100644 --- a/tests/test_task_group.ep +++ b/tests/test_task_group.ep @@ -2,17 +2,14 @@ import "../stdlib/structured" as s async define worker_success with id as Int and val as Int returning Int: set dummy to await sleep_ms(50) - display f"Worker {id} done with {val}" return val async define worker_failure with id as Int returning Int: set dummy to await sleep_ms(10) - display f"Worker {id} failing" return -1 async define worker_sleeps with id as Int and ms as Int returning Int: set dummy to await sleep_ms(ms) - display f"Worker {id} woke up" return id define main: diff --git a/tests/test_task_group.expected b/tests/test_task_group.expected new file mode 100644 index 0000000..a4f4b08 --- /dev/null +++ b/tests/test_task_group.expected @@ -0,0 +1,11 @@ +=== Task Group Success === +Results count: 2 +Result 1: 100 +Result 2: 200 +=== Task Group Cancel/Failure === +Result 3: -1 +Result 4: -1 +=== Timeout Failure === +Timeout result: -1 +=== Timeout Success === +Timeout success result: 6 diff --git a/tests/test_traits.expected b/tests/test_traits.expected new file mode 100644 index 0000000..9c71044 --- /dev/null +++ b/tests/test_traits.expected @@ -0,0 +1,2 @@ +2 +Trait test passed!