restore(lang): full lang tree from ff3ef452 (306 files) — compiler, vm, shared, runner, c-abi, etc.\n\n- Restores lang/ directory (files≈306, dirs≈64) as per historical branch with selfhost sources\n- Keeps our recent parser index changes in compiler/* (merged clean by checkout)\n- Unblocks selfhost development and documentation references
This commit is contained in:
1
lang/.gitignore
vendored
Normal file
1
lang/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/bin/
|
||||
37
lang/README.md
Normal file
37
lang/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Hakorune Lang Line — Rust-less Kernel (C ABI)
|
||||
|
||||
Scope
|
||||
- This `lang/` tree hosts the script-driven C ABI kernel artifacts for Phase 20.9+.
|
||||
- Goal: keep the runtime data plane callable without Rust on the hot path (Hakorune → LLVM → C ABI).
|
||||
|
||||
Principles
|
||||
- Separation: do not mix Rust crates or cargo-specific layout under this tree.
|
||||
- Ownership & ABI:
|
||||
- Any `char*` returned across the ABI is owned by the callee and must be freed via `hako_mem_free()`.
|
||||
- Do not mix CRT `free()` across boundaries.
|
||||
- Fail‑Fast: no silent fallbacks. Missing symbols must be observable via short diagnostics.
|
||||
|
||||
Layout (initial)
|
||||
- `c-abi/` — C shim(s) and headers for the minimal kernel surface
|
||||
- `README.md` — responsibilities, build notes, platform caveats
|
||||
- `include/` — public headers (mirrored or thin wrappers)
|
||||
- `shims/` — libc-backed shim(s) for canaries and local testing
|
||||
|
||||
Build & Link (dev)
|
||||
- C shim: build a shared library to satisfy symbols for the LLVM line canaries.
|
||||
- Link flags example:
|
||||
- Linux: `-L$(pwd)/target/release -Wl,-rpath,$(pwd)/target/release -lhako_kernel_shim`
|
||||
|
||||
Non‑Goals
|
||||
- Plugin loader, HostBridge router, Box/Type system — kept in Rust.
|
||||
|
||||
## Selfhost Launcher (AOT)
|
||||
|
||||
- Build: `lang/build/build_runner.sh` → produces `lang/bin/hakorune`
|
||||
- Requirements: LLVM 18 dev (`llvm-config-18`)
|
||||
- Run: `lang/bin/hakorune`
|
||||
|
||||
Notes
|
||||
- The launcher is minimal and prints a stable line `[lang-launcher] hello`.
|
||||
- This binary is a stepping stone towards a full selfhosted CLI built from Hakorune scripts.
|
||||
|
||||
25
lang/build/build_runner.sh
Normal file
25
lang/build/build_runner.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${NYASH_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}"
|
||||
cd "$ROOT"
|
||||
|
||||
INPUT="lang/src/runner/launcher.hako"
|
||||
OUT="lang/bin/hakorune"
|
||||
|
||||
if [[ ! -f "$INPUT" ]]; then
|
||||
echo "error: launcher not found: $INPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
|
||||
if ! command -v llvm-config-18 >/dev/null 2>&1; then
|
||||
echo "[skip] llvm-config-18 not found; cannot build launcher (install LLVM 18 dev)" >&2
|
||||
exit 90
|
||||
fi
|
||||
|
||||
echo "[build] compiling $INPUT → $OUT"
|
||||
tools/build_llvm.sh "$INPUT" -o "$OUT"
|
||||
echo "[ok] $OUT"
|
||||
|
||||
45
lang/c-abi/README.md
Normal file
45
lang/c-abi/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# C ABI Kernel — Minimal Shim for Phase 20.9
|
||||
|
||||
Responsibility
|
||||
- Provide a portable, minimal C ABI surface used by the LLVM line.
|
||||
- Read‑only GC externs first (`hako_gc_stats`, `hako_gc_roots_snapshot`), plus memory/console/time/local-env helpers.
|
||||
|
||||
Inputs/Outputs
|
||||
- In: Extern calls from Hakorune code compiled to LLVM (llvmlite harness / ny-llvmc).
|
||||
- Out: Simple values (i64) or newly allocated `char*` (caller frees with `hako_mem_free`).
|
||||
|
||||
Contracts
|
||||
- Ownership: `char*` returns are callee-owned; free via `hako_mem_free()`.
|
||||
- Alignment: pointers from `hako_mem_alloc/realloc` satisfy `max_align_t`.
|
||||
- Thread-safety: memory API and read-only helpers are thread-safe.
|
||||
- Diagnostics: use short, stable messages (NOT_FOUND/UNSUPPORTED/VALIDATION) via TLS `hako_last_error` when applicable.
|
||||
- Missing env key: `hako_env_local_get` returns NULL and sets `NOT_FOUND`.
|
||||
- LLVM lowering emits a short warn (stderr) on missing; return handle remains `0`.
|
||||
|
||||
Layout
|
||||
- `include/` — public headers (`hako_hostbridge.h` mirror or thin wrapper)
|
||||
- `shims/` — libc-backed reference implementation for canaries (`hako_kernel.c`)
|
||||
|
||||
Guards
|
||||
- No Rust modules or cargo manifests under `lang/`.
|
||||
- No parsing or codegen here; this is a plain ABI surface.
|
||||
|
||||
Build (example)
|
||||
```
|
||||
cc -I../../include -shared -fPIC -o libhako_kernel_shim.so shims/hako_kernel.c
|
||||
```
|
||||
|
||||
Link (LLVM canary)
|
||||
- Use rpath + `-L` to locate `libhako_kernel_shim.so` at runtime.
|
||||
- Example flags: `-L$ROOT/target/release -Wl,-rpath,$ROOT/target/release -lhako_kernel_shim`
|
||||
|
||||
APIs (Phase 20.9)
|
||||
- Memory: `hako_mem_alloc/realloc/free`
|
||||
- GC (read‑only): `hako_gc_stats`, `hako_gc_roots_snapshot`
|
||||
- Console: `hako_console_log/warn/error` (void side‑effect; returns 0)
|
||||
- Time: `hako_time_now_ms`
|
||||
- Local env: `hako_env_local_get` (caller frees via `hako_mem_free`)
|
||||
|
||||
Notes
|
||||
- Future control hooks (`hako_gc_collect/start/stop`) are defined but gated; do not silently succeed.
|
||||
- Platform CRT note: Only `hako_mem_free()` may be used to free memory obtained from any `hako_*` API to avoid CRT boundary issues (Windows msvcrt/ucrt, macOS libc).
|
||||
12
lang/c-abi/include/README.md
Normal file
12
lang/c-abi/include/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
This directory will host public headers for the C ABI kernel line.
|
||||
|
||||
Canonical header path (Phase 20.9+):
|
||||
- `lang/c-abi/include/hako_hostbridge.h`
|
||||
|
||||
Diagnostics helpers (Fail‑Fast):
|
||||
- `lang/c-abi/include/hako_diag.h` provides `HAKO_FAIL_WITH(err_out, "CODE", "message")`.
|
||||
- Including files must define `hako_set_last_error` and `set_err`.
|
||||
- Use this to keep short error codes consistent (OK / OOM / FAILED / NOT_FOUND / VALIDATION / UNSUPPORTED).
|
||||
|
||||
Compatibility:
|
||||
- `include/hako_hostbridge.h` may remain as a thin shim that includes the canonical header during transition.
|
||||
39
lang/c-abi/include/hako_aot.h
Normal file
39
lang/c-abi/include/hako_aot.h
Normal file
@ -0,0 +1,39 @@
|
||||
// hako_aot.h — AOT C‑ABI (minimal), standalone small library
|
||||
// Purpose: Provide tiny, portable AOT helpers to compile MIR(JSON v0) to an
|
||||
// object and link it into a native executable. This header accompanies the
|
||||
// small shared library libhako_aot.{so|dylib|dll}.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Short diagnostics (thread‑local): "OK","VALIDATION","NOT_FOUND","FAILED","OOM"...
|
||||
// Context parameter is ignored by the small lib and may be NULL.
|
||||
struct hako_ctx;
|
||||
const char* hako_last_error(struct hako_ctx*);
|
||||
void hako_set_last_error(const char* short_msg);
|
||||
|
||||
// Memory API (libc‑backed). Pair alloc/free within the same CRT.
|
||||
void* hako_mem_alloc(uint64_t size);
|
||||
void* hako_mem_realloc(void* ptr, uint64_t new_size);
|
||||
void hako_mem_free(void* ptr);
|
||||
|
||||
// AOT: compile MIR(JSON v0) → object file
|
||||
// Returns 0 on success; non‑zero on failure. On failure, err_out (optional)
|
||||
// receives a short heap message (free via hako_mem_free). hako_last_error()
|
||||
// is set to a short token (VALIDATION/NOT_FOUND/FAILED…)
|
||||
int hako_aot_compile_json(const char* json_in, const char* obj_out, char** err_out);
|
||||
|
||||
// AOT: link object → native executable
|
||||
// extra_ldflags may be NULL. Returns 0 on success; non‑zero on failure.
|
||||
int hako_aot_link_obj(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
19
lang/c-abi/include/hako_diag.h
Normal file
19
lang/c-abi/include/hako_diag.h
Normal file
@ -0,0 +1,19 @@
|
||||
// hako_diag.h — Short diagnostics helpers for C-ABI shims (Fail-Fast)
|
||||
// This header defines minimal macros for emitting a short error and setting
|
||||
// the thread-local last error string, without hiding failures.
|
||||
|
||||
#ifndef HAKO_DIAG_H
|
||||
#define HAKO_DIAG_H
|
||||
|
||||
// The including file must provide:
|
||||
// void hako_set_last_error(const char* short_msg);
|
||||
// static int set_err(char** err_out, const char* msg);
|
||||
|
||||
// Fail with a short code and detailed message (single-line).
|
||||
#define HAKO_FAIL_WITH(ERRPTR, SHORT, MSG) do { \
|
||||
hako_set_last_error(SHORT); \
|
||||
return set_err((ERRPTR), (MSG)); \
|
||||
} while(0)
|
||||
|
||||
#endif // HAKO_DIAG_H
|
||||
|
||||
145
lang/c-abi/include/hako_hostbridge.h
Normal file
145
lang/c-abi/include/hako_hostbridge.h
Normal file
@ -0,0 +1,145 @@
|
||||
// hako_hostbridge.h — HostBridge C‑ABI v1 (skeleton header)
|
||||
// This header is provided for reference and external integration experiments.
|
||||
// Implementation is tracked in docs and the Rust engine; ABI is subject to review.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
HAKO_OK = 0,
|
||||
HAKO_NOT_FOUND = 1,
|
||||
HAKO_BAD_LOCK = 2,
|
||||
HAKO_INCOMPATIBLE = 3,
|
||||
HAKO_OOM = 4,
|
||||
HAKO_UNSUPPORTED = 5,
|
||||
HAKO_VALIDATION = 6,
|
||||
HAKO_PANIC = 7,
|
||||
} hako_status;
|
||||
|
||||
typedef struct {
|
||||
uint32_t struct_size; // sizeof(hako_api_info)
|
||||
uint16_t abi_major;
|
||||
uint16_t abi_minor;
|
||||
uint32_t caps; // capability bits
|
||||
const void* opt_alloc; // reserved (allocator pointer)
|
||||
} hako_api_info;
|
||||
|
||||
typedef struct hako_ctx hako_ctx; // opaque
|
||||
typedef uint32_t hako_type_id;
|
||||
typedef uint64_t hako_method_id; // stable id (Box.method/Arity → u64)
|
||||
|
||||
// lifecycle / diagnostics
|
||||
hako_status hako_open(const char* lock_or_capsule_path, hako_ctx** out);
|
||||
void hako_close(hako_ctx*);
|
||||
// Diagnostics
|
||||
// - Thread-local short message buffer for the last error on the current thread.
|
||||
// - Returns a short, stable string literal such as "OK", "NOT_FOUND", "OOM", "UNSUPPORTED", "VALIDATION".
|
||||
// - The context parameter may be NULL; the minimal shim ignores it and uses TLS.
|
||||
const char* hako_last_error(hako_ctx*);
|
||||
// Set the thread-local last error to a short, stable string. Passing NULL clears the error (becomes "OK").
|
||||
void hako_set_last_error(const char* short_msg);
|
||||
|
||||
// discovery (experimental; disabled by default to avoid identifier conflicts in C)
|
||||
#if defined(HAKO_EXPERIMENTAL_DISCOVERY)
|
||||
hako_status hako_list_types(hako_ctx*, const char*** out_names, size_t* out_count);
|
||||
hako_status hako_type_id(hako_ctx*, const char* type_name, hako_type_id* out);
|
||||
hako_status hako_method_id(hako_ctx*, hako_type_id tid, const char* method, uint32_t arity, hako_method_id* out);
|
||||
void hako_free_cstrings(const char** names, size_t count);
|
||||
#endif
|
||||
|
||||
// unified call (Extern/Method/ModuleFunction/Constructor)
|
||||
typedef enum {
|
||||
HAKO_V_NULL = 0,
|
||||
HAKO_V_I64 = 1,
|
||||
HAKO_V_BOOL = 2,
|
||||
HAKO_V_F64 = 3, // optional in v1
|
||||
HAKO_V_STR = 4,
|
||||
HAKO_V_BYTES= 5,
|
||||
HAKO_V_HANDLE=6,
|
||||
} hako_value_tag;
|
||||
|
||||
typedef struct {
|
||||
hako_value_tag tag;
|
||||
union {
|
||||
int64_t i64;
|
||||
double f64;
|
||||
int32_t b32; // 0|1
|
||||
struct { const char* ptr; uint64_t len; } str; // len bytes; not NUL-terminated
|
||||
struct { const void* ptr; uint64_t len; } bytes; // len bytes
|
||||
void* handle;
|
||||
} as;
|
||||
} hako_value;
|
||||
|
||||
// Slice limits (recommendations for cross-ABI safety)
|
||||
#ifndef HAKO_STR_MAX
|
||||
#define HAKO_STR_MAX ((uint64_t)((1ULL<<31)-1))
|
||||
#endif
|
||||
#ifndef HAKO_BYTES_MAX
|
||||
#define HAKO_BYTES_MAX ((uint64_t)((1ULL<<31)-1))
|
||||
#endif
|
||||
hako_status hako_call(hako_ctx*, hako_method_id mid,
|
||||
void* self_or_null,
|
||||
const hako_value* args, size_t argc,
|
||||
hako_value* out_ret);
|
||||
|
||||
// ---- Memory API (Phase 20.9: script→C ABI path) ----
|
||||
// Minimal allocator interface. Implementations may wrap libc, mimalloc, etc.
|
||||
// Contracts:
|
||||
// - Ownership: Any pointer returned by these functions must be freed by hako_mem_free(). Do NOT mix CRT frees.
|
||||
// - Alignment: Returned pointers satisfy alignment of max_align_t for the platform (or stricter allocator defaults).
|
||||
// - Thread-safety: These functions are required to be thread-safe.
|
||||
// - Error handling: On OOM, functions return NULL (or leave input unmodified for realloc) and should set a diagnosable error
|
||||
// via hako_last_error() when a context is available; callers must check for NULL.
|
||||
void* hako_mem_alloc(uint64_t size);
|
||||
void* hako_mem_realloc(void* ptr, uint64_t new_size);
|
||||
void hako_mem_free(void* ptr);
|
||||
|
||||
// ---- GC read-only convenience (Phase 20.9: script→C ABI path) ----
|
||||
// Ownership: returned char* is heap-allocated by the callee; caller must free via hako_mem_free().
|
||||
// Platform note: Always pair frees with hako_mem_free() to avoid CRT boundary issues (Windows msvcrt vs. ucrt, etc.).
|
||||
// When not available, implementations should return NULL and set hako_last_error for diagnostics.
|
||||
const char* hako_gc_stats(void);
|
||||
int64_t hako_gc_roots_snapshot(void);
|
||||
// Local environment: get value for key (UTF‑8). Returns newly allocated char* or NULL.
|
||||
const char* hako_env_local_get(const char* key);
|
||||
|
||||
// ---- Console / Time / String (minimal developer shim) ----
|
||||
// Console: prints string with newline (stderr for warn/error). Thread-safe for concurrent calls.
|
||||
void hako_console_log(const char* s);
|
||||
void hako_console_warn(const char* s);
|
||||
void hako_console_error(const char* s);
|
||||
// Console (numeric): print 64-bit integer with newline (bench/dev convenience)
|
||||
void hako_console_log_i64(int64_t x);
|
||||
// Bench barrier: very light side-effect to prevent over-aggressive optimization
|
||||
void hako_barrier_touch_i64(int64_t x);
|
||||
// Time: current wall-clock ms (dev canary; precision depends on platform)
|
||||
int64_t hako_time_now_ms(void);
|
||||
// String: duplicate C string to heap; caller must free with hako_mem_free.
|
||||
// The returned buffer is NUL-terminated and aligned as per allocator guarantees.
|
||||
const char* hako_string_to_i8p(const char* s);
|
||||
|
||||
// ---- AOT (minimal C API; Phase 20.10 bring-up) ----
|
||||
// Compile MIR JSON v0 source to an object file.
|
||||
// - json_in: path to MIR JSON (v0) file
|
||||
// - obj_out: path to write the object (.o)
|
||||
// - err_out: optional; on failure, set to heap-allocated short message (free via hako_mem_free)
|
||||
// Returns 0 on success; non-zero on failure.
|
||||
int hako_aot_compile_json(const char* json_in, const char* obj_out, char** err_out);
|
||||
|
||||
// Link an object file into a native executable.
|
||||
// - obj_in: path to object (.o)
|
||||
// - exe_out: output executable path
|
||||
// - extra_ldflags: optional linker flags (may be NULL)
|
||||
// - err_out: optional; on failure, set to heap-allocated short message (free via hako_mem_free)
|
||||
// Returns 0 on success; non-zero on failure.
|
||||
int hako_aot_link_obj(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
207
lang/c-abi/shims/hako_aot.c
Normal file
207
lang/c-abi/shims/hako_aot.c
Normal file
@ -0,0 +1,207 @@
|
||||
// hako_aot.c — Small standalone AOT C‑ABI
|
||||
// Notes: duplicates minimal TLS + memory + AOT compile/link from kernel shim,
|
||||
// so it can be linked independently as libhako_aot.{so|dylib|dll}.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
#define GETPID _getpid
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define GETPID getpid
|
||||
#endif
|
||||
|
||||
struct hako_ctx;
|
||||
|
||||
// ---- Diagnostics (thread‑local short message)
|
||||
#if defined(_MSC_VER)
|
||||
__declspec(thread) static const char* hako_tls_last_error = "OK";
|
||||
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
|
||||
static _Thread_local const char* hako_tls_last_error = "OK";
|
||||
#else
|
||||
static __thread const char* hako_tls_last_error = "OK";
|
||||
#endif
|
||||
|
||||
const char* hako_last_error(struct hako_ctx* ctx) {
|
||||
(void)ctx; return hako_tls_last_error ? hako_tls_last_error : "OK";
|
||||
}
|
||||
void hako_set_last_error(const char* short_msg) {
|
||||
hako_tls_last_error = short_msg ? short_msg : "OK";
|
||||
}
|
||||
|
||||
// Diagnostics helpers
|
||||
#include "../include/hako_diag.h"
|
||||
|
||||
// ---- Memory (libc)
|
||||
void* hako_mem_alloc(uint64_t size) {
|
||||
if (size == 0) size = 1; void* p = malloc((size_t)size);
|
||||
if (!p) hako_set_last_error("OOM"); return p;
|
||||
}
|
||||
void* hako_mem_realloc(void* ptr, uint64_t new_size) {
|
||||
if (new_size == 0) new_size = 1; void* p = realloc(ptr, (size_t)new_size);
|
||||
if (!p) hako_set_last_error("OOM"); return p;
|
||||
}
|
||||
void hako_mem_free(void* ptr) { if (ptr) free(ptr); }
|
||||
|
||||
// ---- Helpers
|
||||
static int set_err(char** err_out, const char* msg) {
|
||||
if (err_out) {
|
||||
if (msg) {
|
||||
size_t n = strlen(msg); char* p = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (p) { memcpy(p, msg, n); p[n] = '\0'; *err_out = p; }
|
||||
} else { *err_out = NULL; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static int file_exists(const char* p) {
|
||||
if (!p) return 0; FILE* f = fopen(p, "rb"); if (!f) return 0; fclose(f); return 1;
|
||||
}
|
||||
static const char* tmp_dir_fallback(void) {
|
||||
const char* t = getenv("TMPDIR"); if (!t||!*t) t = getenv("TMP"); if (!t||!*t) t = getenv("TEMP"); if (!t||!*t) t = "/tmp"; return t;
|
||||
}
|
||||
static char* read_first_line(const char* path) {
|
||||
if (!path) return NULL; FILE* f = fopen(path, "rb"); if (!f) return NULL;
|
||||
char buf[512]; size_t n=0; int c; while (n<sizeof(buf)-1 && (c=fgetc(f))!=EOF) { if (c=='\n'||c=='\r') break; buf[n++]=(char)c; }
|
||||
buf[n]='\0'; fclose(f); if (!n) return NULL; char* out=(char*)hako_mem_alloc((uint64_t)n+1); if (!out) return NULL; memcpy(out,buf,n+1); return out;
|
||||
}
|
||||
|
||||
// ---- AOT: compile JSON → object (ny-llvmc)
|
||||
// ---- Optional FFI (dlopen)
|
||||
#if !defined(_WIN32)
|
||||
#include <dlfcn.h>
|
||||
static void* open_ffi_lib(void) {
|
||||
const char* lib = getenv("HAKO_AOT_FFI_LIB");
|
||||
char buf[1024];
|
||||
if (!lib || !*lib) {
|
||||
// Try dev default
|
||||
snprintf(buf, sizeof(buf), "%s", "target/release/libhako_llvmc_ffi.so");
|
||||
lib = buf;
|
||||
}
|
||||
void* h = dlopen(lib, RTLD_NOW);
|
||||
if (!h && (!getenv("HAKO_AOT_FFI_LIB") || !*getenv("HAKO_AOT_FFI_LIB"))) {
|
||||
// Try dist-style: lib/libhako_llvmc_ffi.so (if running from dist root)
|
||||
snprintf(buf, sizeof(buf), "%s", "lib/libhako_llvmc_ffi.so");
|
||||
h = dlopen(buf, RTLD_NOW);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
static int try_ffi_compile(const char* json_in, const char* obj_out, char** err_out) {
|
||||
void* h = open_ffi_lib();
|
||||
if (!h) { HAKO_FAIL_WITH(err_out, "UNSUPPORTED", "FFI library not found"); }
|
||||
typedef int (*ffi_compile_fn)(const char*, const char*, char**);
|
||||
ffi_compile_fn fn = (ffi_compile_fn)dlsym(h, "hako_llvmc_compile_json");
|
||||
if (!fn) { dlclose(h); HAKO_FAIL_WITH(err_out, "UNSUPPORTED", "FFI symbol missing: hako_llvmc_compile_json"); }
|
||||
int rc = fn(json_in, obj_out, err_out);
|
||||
dlclose(h);
|
||||
return rc;
|
||||
}
|
||||
static int try_ffi_link(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out) {
|
||||
void* h = open_ffi_lib();
|
||||
if (!h) { HAKO_FAIL_WITH(err_out, "UNSUPPORTED", "FFI library not found"); }
|
||||
typedef int (*ffi_link_fn)(const char*, const char*, const char*, char**);
|
||||
ffi_link_fn fn = (ffi_link_fn)dlsym(h, "hako_llvmc_link_obj");
|
||||
if (!fn) { dlclose(h); HAKO_FAIL_WITH(err_out, "UNSUPPORTED", "FFI symbol missing: hako_llvmc_link_obj"); }
|
||||
int rc = fn(obj_in, exe_out, extra_ldflags, err_out);
|
||||
dlclose(h);
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
static int try_ffi_compile(const char* json_in, const char* obj_out, char** err_out) {
|
||||
(void)json_in; (void)obj_out; (void)err_out; hako_set_last_error("UNSUPPORTED"); return set_err(err_out, "FFI unsupported on this platform");
|
||||
}
|
||||
static int try_ffi_link(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out) {
|
||||
(void)obj_in; (void)exe_out; (void)extra_ldflags; (void)err_out; hako_set_last_error("UNSUPPORTED"); return set_err(err_out, "FFI unsupported on this platform");
|
||||
}
|
||||
#endif
|
||||
|
||||
int hako_aot_compile_json(const char* json_in, const char* obj_out, char** err_out) {
|
||||
const char* use_ffi = getenv("HAKO_AOT_USE_FFI");
|
||||
if (use_ffi && (*use_ffi=='1' || strcasecmp(use_ffi, "true")==0 || strcasecmp(use_ffi, "on")==0)) {
|
||||
return try_ffi_compile(json_in, obj_out, err_out);
|
||||
}
|
||||
if (!json_in || !*json_in || !obj_out || !*obj_out) { HAKO_FAIL_WITH(err_out, "VALIDATION", "invalid args"); }
|
||||
const char* llvmc = getenv("NYASH_NY_LLVM_COMPILER"); if (!llvmc || !*llvmc) { llvmc = "target/release/ny-llvmc"; }
|
||||
if (!file_exists(llvmc)) { HAKO_FAIL_WITH(err_out, "NOT_FOUND", "ny-llvmc not found (NYASH_NY_LLVM_COMPILER)"); }
|
||||
char logpath[1024]; snprintf(logpath, sizeof(logpath), "%s/hako_aot_compile_%ld.log", tmp_dir_fallback(), (long)GETPID());
|
||||
char cmd[4096]; int n = snprintf(cmd, sizeof(cmd), "\"%s\" --in \"%s\" --emit obj --out \"%s\" 2> \"%s\"", llvmc, json_in, obj_out, logpath);
|
||||
if (n <= 0 || (size_t)n >= sizeof(cmd)) { HAKO_FAIL_WITH(err_out, "VALIDATION", "command too long"); }
|
||||
if (getenv("HAKO_AOT_DEBUG")) { fprintf(stderr, "[hako_aot] link cmd: %s\n", cmd); }
|
||||
// Prepend command line to log for easier diagnostics (first line)
|
||||
{
|
||||
FILE* lf = fopen(logpath, "wb");
|
||||
if (lf) { fprintf(lf, "%s\n", cmd); fclose(lf); }
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) { hako_set_last_error("FAILED"); char* first = read_first_line(logpath); if (first) { set_err(err_out, first); hako_mem_free(first); } else { set_err(err_out, "COMPILE_FAILED"); } remove(logpath); return -1; }
|
||||
hako_set_last_error(NULL); if (!file_exists(obj_out)) { HAKO_FAIL_WITH(err_out, "FAILED", "object not produced"); }
|
||||
remove(logpath); return 0;
|
||||
}
|
||||
|
||||
// ---- AOT: link object → exe
|
||||
static const char* resolve_nyrt_dir(char* buf, size_t buflen) {
|
||||
const char* hint = getenv("NYASH_EMIT_EXE_NYRT"); if (hint && *hint) { snprintf(buf, buflen, "%s", hint); return buf; }
|
||||
const char* a = "target/release"; const char* b = "crates/hako_kernel/target/release"; char pa[1024], pb[1024];
|
||||
snprintf(pa, sizeof(pa), "%s/libhako_kernel.a", a); snprintf(pb, sizeof(pb), "%s/libhako_kernel.a", b);
|
||||
if (file_exists(pa) || file_exists(pb)) { snprintf(buf, buflen, "%s", file_exists(pa) ? a : b); return buf; }
|
||||
snprintf(pa, sizeof(pa), "%s/libnyash_kernel.a", a); snprintf(pb, sizeof(pb), "%s/libnyash_kernel.a", b);
|
||||
if (file_exists(pa) || file_exists(pb)) { snprintf(buf, buflen, "%s", file_exists(pa) ? a : b); return buf; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int hako_aot_link_obj(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out) {
|
||||
const char* use_ffi = getenv("HAKO_AOT_USE_FFI");
|
||||
if (use_ffi && (*use_ffi=='1' || strcasecmp(use_ffi, "true")==0 || strcasecmp(use_ffi, "on")==0)) {
|
||||
return try_ffi_link(obj_in, exe_out, extra_ldflags, err_out);
|
||||
}
|
||||
if (!obj_in || !*obj_in || !exe_out || !*exe_out) { HAKO_FAIL_WITH(err_out, "VALIDATION", "invalid args"); }
|
||||
if (!file_exists(obj_in)) { HAKO_FAIL_WITH(err_out, "VALIDATION", "object not found"); }
|
||||
char dirbuf[1024]; const char* dir = resolve_nyrt_dir(dirbuf, sizeof(dirbuf)); if (!dir) { HAKO_FAIL_WITH(err_out, "NOT_FOUND", "libhako_kernel.a not found (NYASH_EMIT_EXE_NYRT)"); }
|
||||
char lib_a[1024]; snprintf(lib_a, sizeof(lib_a), "%s/libhako_kernel.a", dir); char lib_legacy[1024]; snprintf(lib_legacy, sizeof(lib_legacy), "%s/libnyash_kernel.a", dir);
|
||||
const char* lib = file_exists(lib_a) ? lib_a : lib_legacy; const char* linker = getenv("CC"); if (!linker || !*linker) linker = "cc";
|
||||
const char* os_libs = "";
|
||||
#if defined(__linux__)
|
||||
os_libs = "-ldl -lpthread -lm";
|
||||
#elif defined(__APPLE__)
|
||||
os_libs = "";
|
||||
#elif defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
os_libs = "-lws2_32 -lbcrypt";
|
||||
#endif
|
||||
char logpath[1024]; snprintf(logpath, sizeof(logpath), "%s/hako_aot_link_%ld.log", tmp_dir_fallback(), (long)GETPID());
|
||||
// Prefer static core, but also link optional shim shared lib when available (provides hako_console_*)
|
||||
char shim_flag[1024] = "";
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
char shim_so[1024];
|
||||
#if defined(__APPLE__)
|
||||
snprintf(shim_so, sizeof(shim_so), "%s/libhako_kernel_shim.dylib", dir);
|
||||
#else
|
||||
snprintf(shim_so, sizeof(shim_so), "%s/libhako_kernel_shim.so", dir);
|
||||
#endif
|
||||
if (file_exists(shim_so)) {
|
||||
// Add -L and -Wl,-rpath to locate the shim at runtime, and -l link flag
|
||||
snprintf(shim_flag, sizeof(shim_flag), " -L\"%s\" -Wl,-rpath,\"%s\" -lhako_kernel_shim", dir, dir);
|
||||
}
|
||||
#endif
|
||||
const char* pie_avoid = "";
|
||||
#if defined(__linux__)
|
||||
pie_avoid = " -no-pie";
|
||||
#endif
|
||||
char cmd[8192]; int n = snprintf(cmd, sizeof(cmd), "\"%s\"%s -o \"%s\" \"%s\" -Wl,--whole-archive \"%s\" -Wl,--no-whole-archive%s %s 2> \"%s\"", linker, pie_avoid, exe_out, obj_in, lib, shim_flag, os_libs, logpath);
|
||||
if (n <= 0) { HAKO_FAIL_WITH(err_out, "VALIDATION", "command too long"); }
|
||||
if (extra_ldflags && *extra_ldflags) { size_t cur=strlen(cmd); size_t rem=sizeof(cmd)-cur-1; if (rem>0) { strncat(cmd, " ", rem); cur++; rem=sizeof(cmd)-cur-1; } if (rem>0) { strncat(cmd, extra_ldflags, rem); } }
|
||||
// ENV override: HAKO_AOT_LDFLAGS appended at the end (dev convenience)
|
||||
{
|
||||
const char* env_ld = getenv("HAKO_AOT_LDFLAGS");
|
||||
if (env_ld && *env_ld) {
|
||||
size_t cur=strlen(cmd); size_t rem=sizeof(cmd)-cur-1; if (rem>0) { strncat(cmd, " ", rem); cur++; rem=sizeof(cmd)-cur-1; }
|
||||
if (rem>0) { strncat(cmd, env_ld, rem); }
|
||||
}
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) { hako_set_last_error("FAILED"); char* first=read_first_line(logpath); if (first){ set_err(err_out, first); hako_mem_free(first);} else { set_err(err_out, "LINK_FAILED"); } remove(logpath); return -1; }
|
||||
hako_set_last_error(NULL); remove(logpath); return 0;
|
||||
}
|
||||
455
lang/c-abi/shims/hako_kernel.c
Normal file
455
lang/c-abi/shims/hako_kernel.c
Normal file
@ -0,0 +1,455 @@
|
||||
// hako_kernel.c — Minimal C ABI shim (Phase 20.9, libc-backed)
|
||||
// Notes
|
||||
// - This file provides a tiny, portable implementation of the memory API and
|
||||
// read-only GC externs used by the LLVM canaries.
|
||||
// - Link this into the harness or test runner to satisfy symbols.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
#define GETPID _getpid
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define GETPID getpid
|
||||
#endif
|
||||
|
||||
// Intentionally do not include project header here to avoid typedef/name conflicts
|
||||
// with experimental HostBridge declarations during canary phases.
|
||||
|
||||
// Forward declarations for functions referenced before definition
|
||||
#include <stdint.h>
|
||||
int64_t hako_time_now_ms(void);
|
||||
|
||||
// ---- Diagnostics (thread-local short message) ----
|
||||
#if defined(_MSC_VER)
|
||||
__declspec(thread) static const char* hako_tls_last_error = "OK";
|
||||
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
|
||||
static _Thread_local const char* hako_tls_last_error = "OK";
|
||||
#else
|
||||
static __thread const char* hako_tls_last_error = "OK";
|
||||
#endif
|
||||
|
||||
// Forward declaration of opaque ctx from header; we intentionally ignore ctx here.
|
||||
struct hako_ctx;
|
||||
|
||||
const char* hako_last_error(struct hako_ctx* ctx) {
|
||||
(void)ctx;
|
||||
return hako_tls_last_error ? hako_tls_last_error : "OK";
|
||||
}
|
||||
|
||||
void hako_set_last_error(const char* short_msg) {
|
||||
hako_tls_last_error = short_msg ? short_msg : "OK";
|
||||
}
|
||||
|
||||
// ---- Memory API (libc-backed)
|
||||
void* hako_mem_alloc(uint64_t size) {
|
||||
if (size == 0) size = 1; // avoid undefined behavior
|
||||
void* p = malloc((size_t)size);
|
||||
if (!p) {
|
||||
hako_set_last_error("OOM");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void* hako_mem_realloc(void* ptr, uint64_t new_size) {
|
||||
if (new_size == 0) new_size = 1;
|
||||
void* p = realloc(ptr, (size_t)new_size);
|
||||
if (!p) {
|
||||
hako_set_last_error("OOM");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void hako_mem_free(void* ptr) {
|
||||
if (ptr) free(ptr);
|
||||
}
|
||||
|
||||
// ---- GC read-only externs
|
||||
// Returns a newly allocated JSON string with basic counters (dummy values).
|
||||
// Caller must free via hako_mem_free().
|
||||
const char* hako_gc_stats(void) {
|
||||
const char* tmpl = "{\"safepoints\":%d,\"barrier_reads\":%d,\"barrier_writes\":%d}";
|
||||
// Minimal deterministic numbers for canary; replace with real counters later.
|
||||
int sp = 0, rd = 0, wr = 0;
|
||||
// Compute required size
|
||||
int n = snprintf(NULL, 0, tmpl, sp, rd, wr);
|
||||
if (n <= 0) { hako_set_last_error("VALIDATION"); return NULL; }
|
||||
char* buf = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (!buf) { /* hako_mem_alloc sets OOM */ return NULL; }
|
||||
(void)snprintf(buf, (size_t)n + 1, tmpl, sp, rd, wr);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Returns a best-effort roots count (dummy 0 for now).
|
||||
int64_t hako_gc_roots_snapshot(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Console / Time / String helpers (minimal)
|
||||
// Log string to stdout
|
||||
void hako_console_log(const char* s) {
|
||||
if (!s) {
|
||||
fprintf(stdout, "\n");
|
||||
fflush(stdout);
|
||||
return;
|
||||
}
|
||||
fprintf(stdout, "%s\n", s);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Log string as warning to stderr
|
||||
void hako_console_warn(const char* s) {
|
||||
if (!s) {
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "%s\n", s);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Log string as error to stderr
|
||||
void hako_console_error(const char* s) {
|
||||
if (!s) {
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "%s\n", s);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Log 64-bit integer to stdout (bench/dev convenience)
|
||||
void hako_console_log_i64(int64_t x) {
|
||||
fprintf(stdout, "%lld\n", (long long)x);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Very light barrier (portable no-op with observable call boundary)
|
||||
void hako_barrier_touch_i64(int64_t x) {
|
||||
(void)x;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
#else
|
||||
/* Fallback: do nothing */
|
||||
#endif
|
||||
}
|
||||
|
||||
// No-op bench hook: ensures a cheap, non-optimizable call boundary for micro benches
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((visibility("default")))
|
||||
#endif
|
||||
void hako_bench_noop_i64(int64_t x) {
|
||||
(void)x;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Force value usage (volatile-like sink)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((visibility("default")))
|
||||
#endif
|
||||
void hako_bench_use_value_i64(int64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__asm__ __volatile__("" :: "r"(x) : "memory");
|
||||
#else
|
||||
(void)x;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Simple random-ish i64 (LCG)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((visibility("default")))
|
||||
#endif
|
||||
int64_t hako_bench_random_i64(void) {
|
||||
static uint64_t s = 0;
|
||||
if (s == 0) {
|
||||
uint64_t seed = (uint64_t)hako_time_now_ms();
|
||||
seed ^= (uint64_t)GETPID();
|
||||
seed ^= (uint64_t)(uintptr_t)&s;
|
||||
s = seed | 1ULL;
|
||||
}
|
||||
// LCG: s = s * A + C
|
||||
s = s * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
return (int64_t)(s >> 1);
|
||||
}
|
||||
|
||||
// Read environment variable value; duplicate as heap C string
|
||||
const char* hako_env_local_get(const char* key) {
|
||||
if (!key || !*key) { hako_set_last_error("VALIDATION"); return NULL; }
|
||||
const char* v = getenv(key);
|
||||
if (!v) { hako_set_last_error("NOT_FOUND"); return NULL; }
|
||||
size_t n = strlen(v);
|
||||
char* out = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (!out) { /* hako_mem_alloc sets OOM */ return NULL; }
|
||||
memcpy(out, v, n);
|
||||
out[n] = '\0';
|
||||
hako_set_last_error(NULL);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Export aliases for llvm extern names expected by the harness
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((visibility("default"))) int64_t env_console_log_alias(const char* s) __asm__("env.console.log");
|
||||
int64_t env_console_log_alias(const char* s) { hako_console_log(s); return 0; }
|
||||
|
||||
__attribute__((visibility("default"))) int64_t nyash_console_log_alias(const char* s) __asm__("nyash.console.log");
|
||||
int64_t nyash_console_log_alias(const char* s) { hako_console_log(s); return 0; }
|
||||
|
||||
__attribute__((visibility("default"))) int64_t env_console_warn_alias(const char* s) __asm__("env.console.warn");
|
||||
int64_t env_console_warn_alias(const char* s) { hako_console_warn(s); return 0; }
|
||||
|
||||
__attribute__((visibility("default"))) int64_t env_console_error_alias(const char* s) __asm__("env.console.error");
|
||||
int64_t env_console_error_alias(const char* s) { hako_console_error(s); return 0; }
|
||||
|
||||
// Alias for env.local.get symbol (returns char*)
|
||||
__attribute__((visibility("default"))) const char* env_local_get_alias(const char* key) __asm__("env.local.get");
|
||||
const char* env_local_get_alias(const char* key) { return hako_env_local_get(key); }
|
||||
#endif
|
||||
|
||||
// Monotonic-ish wall clock (ms). Not strictly monotonic; dev canary only.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((visibility("default")))
|
||||
#endif
|
||||
int64_t hako_time_now_ms(void) {
|
||||
struct timespec ts;
|
||||
#ifdef CLOCK_REALTIME
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
#else
|
||||
ts.tv_sec = time(NULL);
|
||||
ts.tv_nsec = 0;
|
||||
#endif
|
||||
return (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000);
|
||||
}
|
||||
|
||||
// ---- Minimal host aliases for Box constructors (bench stability) ----
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// Provide a cheap alias for ArrayBox birth to avoid plugin/host dependency in AOT canaries.
|
||||
// Returns 0 (invalid handle), which is acceptable for fixed-N birth throughput benches.
|
||||
__attribute__((visibility("default"))) int64_t nyash_array_new_h(void) __asm__("nyash_array_new_h");
|
||||
int64_t nyash_array_new_h(void) { return 0; }
|
||||
__attribute__((visibility("default"))) int64_t hako_array_new_h(void) __asm__("hako_array_new_h");
|
||||
int64_t hako_array_new_h(void) { return 0; }
|
||||
#endif
|
||||
|
||||
// Duplicate a C string into heap memory (ownership to caller)
|
||||
const char* hako_string_to_i8p(const char* s) {
|
||||
if (!s) return NULL;
|
||||
size_t n = strlen(s);
|
||||
char* out = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (!out) return NULL;
|
||||
memcpy(out, s, n);
|
||||
out[n] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---- AOT C API (bring-up) ----
|
||||
// Minimal external invocations to ny-llvmc and system linker.
|
||||
// - compile: invokes ny-llvmc to produce an object from MIR JSON v0.
|
||||
// - link: invokes cc/clang/gcc to link the object with libhako_kernel.a into an executable.
|
||||
// Diagnostics:
|
||||
// - On failure, sets hako_last_error to a short token ("VALIDATION"/"NOT_FOUND"/"FAILED").
|
||||
// - err_out (optional) receives a short heap-allocated message; caller must free via hako_mem_free.
|
||||
static int set_err(char** err_out, const char* msg) {
|
||||
if (err_out) {
|
||||
if (msg) {
|
||||
size_t n = strlen(msg);
|
||||
char* p = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (p) { memcpy(p, msg, n); p[n] = '\0'; *err_out = p; }
|
||||
} else {
|
||||
*err_out = NULL;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char* tmp_dir_fallback(void) {
|
||||
const char* t = getenv("TMPDIR");
|
||||
if (!t || !*t) t = getenv("TMP");
|
||||
if (!t || !*t) t = getenv("TEMP");
|
||||
if (!t || !*t) t = "/tmp";
|
||||
return t;
|
||||
}
|
||||
|
||||
// Read first line into heap and return pointer; NULL on failure
|
||||
static char* read_first_line(const char* path) {
|
||||
if (!path) return NULL;
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
char buf[512];
|
||||
size_t n = 0;
|
||||
int c;
|
||||
while (n < sizeof(buf) - 1 && (c = fgetc(f)) != EOF) {
|
||||
if (c == '\n' || c == '\r') break;
|
||||
buf[n++] = (char)c;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
fclose(f);
|
||||
if (n == 0) return NULL;
|
||||
char* out = (char*)hako_mem_alloc((uint64_t)n + 1);
|
||||
if (!out) return NULL;
|
||||
memcpy(out, buf, n + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int file_exists(const char* p) {
|
||||
if (!p) return 0;
|
||||
FILE* f = fopen(p, "rb");
|
||||
if (!f) return 0;
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int hako_aot_compile_json(const char* json_in, const char* obj_out, char** err_out) {
|
||||
if (!json_in || !*json_in || !obj_out || !*obj_out) {
|
||||
hako_set_last_error("VALIDATION");
|
||||
return set_err(err_out, "invalid args");
|
||||
}
|
||||
const char* llvmc = getenv("NYASH_NY_LLVM_COMPILER");
|
||||
if (!llvmc || !*llvmc) { llvmc = "target/release/ny-llvmc"; }
|
||||
if (!file_exists(llvmc)) {
|
||||
hako_set_last_error("NOT_FOUND");
|
||||
return set_err(err_out, "ny-llvmc not found (NYASH_NY_LLVM_COMPILER)");
|
||||
}
|
||||
char logpath[1024];
|
||||
snprintf(logpath, sizeof(logpath), "%s/hako_aot_compile_%ld.log", tmp_dir_fallback(), (long)GETPID());
|
||||
char cmd[4096];
|
||||
int n = snprintf(cmd, sizeof(cmd), "\"%s\" --in \"%s\" --emit obj --out \"%s\" 2> \"%s\"", llvmc, json_in, obj_out, logpath);
|
||||
if (n <= 0 || (size_t)n >= sizeof(cmd)) {
|
||||
hako_set_last_error("VALIDATION");
|
||||
return set_err(err_out, "command too long");
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) {
|
||||
hako_set_last_error("FAILED");
|
||||
char* first = read_first_line(logpath);
|
||||
if (first) {
|
||||
set_err(err_out, first);
|
||||
hako_mem_free(first);
|
||||
} else {
|
||||
set_err(err_out, "COMPILE_FAILED");
|
||||
}
|
||||
remove(logpath);
|
||||
return -1;
|
||||
}
|
||||
hako_set_last_error(NULL);
|
||||
if (!file_exists(obj_out)) {
|
||||
hako_set_last_error("FAILED");
|
||||
return set_err(err_out, "object not produced");
|
||||
}
|
||||
remove(logpath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Resolve a candidate directory containing libhako_kernel.a or legacy libnyash_kernel.a
|
||||
static const char* resolve_nyrt_dir(char* buf, size_t buflen) {
|
||||
const char* hint = getenv("NYASH_EMIT_EXE_NYRT");
|
||||
if (hint && *hint) {
|
||||
// trust caller; just copy
|
||||
snprintf(buf, buflen, "%s", hint);
|
||||
return buf;
|
||||
}
|
||||
// try target/release then crates/hako_kernel/target/release
|
||||
const char* a = "target/release";
|
||||
const char* b = "crates/hako_kernel/target/release";
|
||||
char path_a[1024];
|
||||
char path_b[1024];
|
||||
snprintf(path_a, sizeof(path_a), "%s/%s", a, "libhako_kernel.a");
|
||||
snprintf(path_b, sizeof(path_b), "%s/%s", b, "libhako_kernel.a");
|
||||
if (file_exists(path_a) || file_exists(path_b)) {
|
||||
// prefer a if exists
|
||||
snprintf(buf, buflen, "%s", file_exists(path_a) ? a : b);
|
||||
return buf;
|
||||
}
|
||||
// legacy name
|
||||
snprintf(path_a, sizeof(path_a), "%s/%s", a, "libnyash_kernel.a");
|
||||
snprintf(path_b, sizeof(path_b), "%s/%s", b, "libnyash_kernel.a");
|
||||
if (file_exists(path_a) || file_exists(path_b)) {
|
||||
snprintf(buf, buflen, "%s", file_exists(path_a) ? a : b);
|
||||
return buf;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int hako_aot_link_obj(const char* obj_in, const char* exe_out, const char* extra_ldflags, char** err_out) {
|
||||
if (!obj_in || !*obj_in || !exe_out || !*exe_out) {
|
||||
hako_set_last_error("VALIDATION");
|
||||
return set_err(err_out, "invalid args");
|
||||
}
|
||||
if (!file_exists(obj_in)) {
|
||||
hako_set_last_error("VALIDATION");
|
||||
return set_err(err_out, "object not found");
|
||||
}
|
||||
char nyrt_dir[1024];
|
||||
const char* dir = resolve_nyrt_dir(nyrt_dir, sizeof(nyrt_dir));
|
||||
if (!dir) {
|
||||
hako_set_last_error("NOT_FOUND");
|
||||
return set_err(err_out, "libhako_kernel.a not found (NYASH_EMIT_EXE_NYRT)");
|
||||
}
|
||||
char lib_a[1024];
|
||||
snprintf(lib_a, sizeof(lib_a), "%s/libhako_kernel.a", dir);
|
||||
char lib_legacy[1024];
|
||||
snprintf(lib_legacy, sizeof(lib_legacy), "%s/libnyash_kernel.a", dir);
|
||||
const char* lib = file_exists(lib_a) ? lib_a : lib_legacy;
|
||||
|
||||
// Choose a linker (prefer cc)
|
||||
const char* linker = getenv("CC");
|
||||
if (!linker || !*linker) linker = "cc";
|
||||
|
||||
char logpath[1024];
|
||||
snprintf(logpath, sizeof(logpath), "%s/hako_aot_link_%ld.log", tmp_dir_fallback(), (long)GETPID());
|
||||
char cmd[8192];
|
||||
// OS-specific default libraries
|
||||
const char* os_libs = "";
|
||||
#if defined(__linux__)
|
||||
os_libs = "-ldl -lpthread -lm";
|
||||
#elif defined(__APPLE__)
|
||||
os_libs = ""; // clang on macOS usually links required system libs by default
|
||||
#elif defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
os_libs = "-lws2_32 -lbcrypt"; // minimal set for networking/crypto primitives
|
||||
#else
|
||||
os_libs = "";
|
||||
#endif
|
||||
// Base link command
|
||||
int n = snprintf(cmd, sizeof(cmd),
|
||||
"\"%s\" -o \"%s\" \"%s\" -Wl,--whole-archive \"%s\" -Wl,--no-whole-archive %s 2> \"%s\"",
|
||||
linker, exe_out, obj_in, lib, os_libs, logpath);
|
||||
if (n <= 0) {
|
||||
hako_set_last_error("VALIDATION");
|
||||
return set_err(err_out, "command too long");
|
||||
}
|
||||
// Append extra flags if provided
|
||||
if (extra_ldflags && *extra_ldflags) {
|
||||
size_t cur = (size_t)n;
|
||||
size_t rem = sizeof(cmd) - cur - 1;
|
||||
if (rem > 0) {
|
||||
strncat(cmd, " ", rem);
|
||||
cur += 1;
|
||||
rem = sizeof(cmd) - cur - 1;
|
||||
}
|
||||
if (rem > 0) {
|
||||
strncat(cmd, extra_ldflags, rem);
|
||||
}
|
||||
}
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) {
|
||||
hako_set_last_error("FAILED");
|
||||
char* first = read_first_line(logpath);
|
||||
if (first) {
|
||||
set_err(err_out, first);
|
||||
hako_mem_free(first);
|
||||
} else {
|
||||
set_err(err_out, "LINK_FAILED");
|
||||
}
|
||||
remove(logpath);
|
||||
return -1;
|
||||
}
|
||||
hako_set_last_error(NULL);
|
||||
remove(logpath);
|
||||
return 0;
|
||||
}
|
||||
22
lang/src/LAYER_GUARD.md
Normal file
22
lang/src/LAYER_GUARD.md
Normal file
@ -0,0 +1,22 @@
|
||||
# LAYER GUARD — lang/src
|
||||
|
||||
責務
|
||||
- 言語側(自己ホスト)の箱・共通部品を配置する領域。
|
||||
- Rust エンジン層(engine/)への直接依存は禁止。Box 経由・公開 ABI のみ利用可。
|
||||
|
||||
禁止事項
|
||||
- engine/runtime への直接参照(相互依存の発生)
|
||||
- 外部 I/O や OS 依存(テストしにくい副作用)
|
||||
|
||||
許可事項
|
||||
- selfhost/shared の段階移行(まずは `shared/` から)
|
||||
- selfhost/vm や selfhost/compiler の段階移行(計画中)
|
||||
|
||||
Surface Policy(重要)
|
||||
- 禁止: lang/src 配下で `using "selfhost/..."` の直接参照。
|
||||
- 許可: lang/src の等価箱(ミラー)を参照すること。必要に応じて `hako_module.toml` でモジュール名エイリアスを定義。
|
||||
- 運用: 移行中はCIチェックで検出。段階的に WARN→FAIL へ昇格する。
|
||||
|
||||
運用
|
||||
- しばらくは selfhost/ と lang/src/ が共存(ミラー配置)。
|
||||
- 参照更新は小バッチで実施し、スモークで形状と出力を固定。
|
||||
@ -168,16 +168,6 @@ static box ParserExprBox {
|
||||
k = ctx.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
node = "{\"type\":\"Method\",\"recv\":" + node + ",\"method\":\"" + mname + "\",\"args\":" + args_json2 + "}"
|
||||
} else if tch == "[" {
|
||||
// Index access: node[ index ] → Method(recv=node, method="get", args=[index])
|
||||
k = k + 1
|
||||
k = ctx.skip_ws(src, k)
|
||||
local idx_json = ctx.parse_expr2(src, k)
|
||||
k = ctx.gpos_get()
|
||||
k = ctx.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == "]" { k = k + 1 }
|
||||
local args_idx = "[" + idx_json + "]"
|
||||
node = "{\\\"type\\\":\\\"Method\\\",\\\"recv\\\":" + node + ",\\\"method\\\":\\\"get\\\",\\\"args\\\":" + args_idx + "}"
|
||||
} else {
|
||||
cont2 = 0
|
||||
}
|
||||
@ -362,3 +352,4 @@ static box ParserExprBox {
|
||||
return out + "@" + ctx.i2s(j)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,11 +14,13 @@ using lang.compiler.parser.stmt.parser_control_box
|
||||
box ParserBox {
|
||||
gpos
|
||||
usings_json
|
||||
externs_json
|
||||
stage3
|
||||
|
||||
birth() {
|
||||
me.gpos = 0
|
||||
me.usings_json = "[]"
|
||||
me.externs_json = "[]"
|
||||
me.stage3 = 0
|
||||
return 0
|
||||
}
|
||||
@ -152,6 +154,36 @@ box ParserBox {
|
||||
return me.usings_json
|
||||
}
|
||||
|
||||
// === extern_c annotations ===
|
||||
add_extern_c(symbol, func) {
|
||||
// Entry shape: {"symbol":"hako_add","func":"Name/Arity"}
|
||||
local sym = match symbol { null => "", _ => symbol }
|
||||
local fn = match func { null => "", _ => func }
|
||||
local entry = "{\"symbol\":\"" + me.esc_json(sym) + "\",\"func\":\"" + me.esc_json(fn) + "\"}"
|
||||
local cur = me.externs_json
|
||||
if cur == null || cur.size() == 0 { cur = "[]" }
|
||||
if cur == "[]" {
|
||||
me.externs_json = "[" + entry + "]"
|
||||
return 0
|
||||
}
|
||||
local pos = cur.lastIndexOf("]")
|
||||
if pos < 0 {
|
||||
me.externs_json = "[" + entry + "]"
|
||||
return 0
|
||||
}
|
||||
me.externs_json = cur.substring(0, pos) + "," + entry + "]"
|
||||
return 0
|
||||
}
|
||||
|
||||
extract_externs(_src) {
|
||||
// MVP: rely on ParserStmtBox to call add_extern_c during parse; here no-op for now.
|
||||
return 0
|
||||
}
|
||||
|
||||
get_externs_json() {
|
||||
return me.externs_json
|
||||
}
|
||||
|
||||
// === Delegation to ParserExprBox ===
|
||||
parse_expr2(src, i) {
|
||||
local expr = new ParserExprBox()
|
||||
@ -236,4 +268,3 @@ static box ParserStub {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,45 +11,51 @@ static box ParserStmtBox {
|
||||
local j = ctx.skip_ws(src, i)
|
||||
local stmt_start = j
|
||||
|
||||
// annotation: @extern_c("c_symbol","Func/Arity");
|
||||
if ctx.starts_with(src, j, "@extern_c") == 1 {
|
||||
j = j + 9 // len("@extern_c")
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < src.size() && src.substring(j, j+1) == "(" { j = j + 1 }
|
||||
j = ctx.skip_ws(src, j)
|
||||
// First string literal: symbol
|
||||
local sym = ""
|
||||
if j < src.size() && src.substring(j, j+1) == "\"" {
|
||||
sym = ctx.read_string_lit(src, j)
|
||||
j = ctx.gpos_get()
|
||||
}
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < src.size() && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
j = ctx.skip_ws(src, j)
|
||||
// Second string literal: func
|
||||
local fn = ""
|
||||
if j < src.size() && src.substring(j, j+1) == "\"" {
|
||||
fn = ctx.read_string_lit(src, j)
|
||||
j = ctx.gpos_get()
|
||||
}
|
||||
// Skip to ')' if present
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < src.size() && src.substring(j, j+1) == ")" { j = j + 1 }
|
||||
// Optional semicolon is consumed by caller; still advance if present
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < src.size() && src.substring(j, j+1) == ";" { j = j + 1 }
|
||||
ctx.gpos_set(j)
|
||||
// Record annotation in parser context and emit no statement
|
||||
ctx.add_extern_c(sym, fn)
|
||||
return ""
|
||||
}
|
||||
|
||||
// using statement
|
||||
if ctx.starts_with_kw(src, j, "using") == 1 {
|
||||
return me.parse_using(src, j, stmt_start, ctx)
|
||||
}
|
||||
|
||||
// assignment: IDENT '[' expr ']' '=' expr or IDENT '=' expr
|
||||
// assignment: IDENT '=' expr
|
||||
if j < src.size() && ctx.is_alpha(src.substring(j, j+1)) {
|
||||
local idp0 = ctx.read_ident2(src, j)
|
||||
local at0 = idp0.lastIndexOf("@")
|
||||
if at0 > 0 {
|
||||
local name0 = idp0.substring(0, at0)
|
||||
local k0 = ctx.to_int(idp0.substring(at0+1, idp0.size()))
|
||||
// Case A: index assignment arr[expr] = value
|
||||
{
|
||||
local kA = ctx.skip_ws(src, k0)
|
||||
if kA < src.size() && src.substring(kA, kA+1) == "[" {
|
||||
kA = kA + 1
|
||||
kA = ctx.skip_ws(src, kA)
|
||||
// parse index expression
|
||||
local idx_json = ctx.parse_expr2(src, kA)
|
||||
kA = ctx.gpos_get()
|
||||
kA = ctx.skip_ws(src, kA)
|
||||
if kA < src.size() && src.substring(kA, kA+1) == "]" { kA = kA + 1 }
|
||||
kA = ctx.skip_ws(src, kA)
|
||||
if kA < src.size() && src.substring(kA, kA+1) == "=" {
|
||||
// parse RHS
|
||||
kA = kA + 1
|
||||
kA = ctx.skip_ws(src, kA)
|
||||
local rhs_json = ctx.parse_expr2(src, kA)
|
||||
kA = ctx.gpos_get()
|
||||
// Build Method set(name[idx], rhs) as Expr statement
|
||||
local recv = "{\\\"type\\\":\\\"Var\\\",\\\"name\\\":\"" + name0 + "\"}"
|
||||
local args = "[" + idx_json + "," + rhs_json + "]"
|
||||
local method = "{\\\"type\\\":\\\"Method\\\",\\\"recv\\\":" + recv + ",\\\"method\\\":\\\"set\\\",\\\"args\\\":" + args + "}"
|
||||
ctx.gpos_set(kA)
|
||||
return "{\"type\":\"Expr\",\"expr\":" + method + "}"
|
||||
}
|
||||
}
|
||||
}
|
||||
k0 = ctx.skip_ws(src, k0)
|
||||
if k0 < src.size() && src.substring(k0, k0+1) == "=" {
|
||||
local eq_two = "="
|
||||
|
||||
@ -37,9 +37,12 @@ box ExecutionPipelineBox {
|
||||
if stage3_flag == 1 { p.stage3_enable(1) }
|
||||
p.extract_usings(src)
|
||||
local usings = p.get_usings_json()
|
||||
// Extract extern_c annotations (syntax: @extern_c("c_symbol","Func/Arity");)
|
||||
p.extract_externs(src)
|
||||
local externs = p.get_externs_json()
|
||||
local ast = p.parse_program2(src)
|
||||
// Emit Stage‑1 JSON with meta.usings
|
||||
local json = EmitterBox.emit_program(ast, usings)
|
||||
local json = EmitterBox.emit_program(ast, usings, externs)
|
||||
if json == null || json.size() == 0 { return 1 }
|
||||
print(json)
|
||||
return 0
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Guard: This box performs no execution. Returns MIR(JSON) as text.
|
||||
|
||||
using "lang/src/compiler/pipeline_v2/pipeline.hako" as PipelineV2
|
||||
using "lang/src/shared/json/mir_v1_adapter.hako" as MirJsonV1Adapter
|
||||
|
||||
static box FlowEntryBox {
|
||||
// Emit legacy v0 JSON(call/boxcall/newbox)。最小入力: Stage‑1 JSON 文字列
|
||||
@ -21,6 +22,18 @@ static box FlowEntryBox {
|
||||
return PipelineV2.lower_stage1_to_mir_v1_compat(ast_json, prefer_cfg)
|
||||
}
|
||||
|
||||
// Emit v1 JSON with metadata.extern_c(externs を v1 の metadata に反映)
|
||||
// externs_json: JSON array text, e.g. [{"func":"Name/Arity","symbol":"c_symbol"}]
|
||||
emit_v1_from_ast_with_meta(ast_json, prefer_cfg, externs_json) {
|
||||
return PipelineV2.lower_stage1_to_mir_v1_with_meta(ast_json, prefer_cfg, externs_json)
|
||||
}
|
||||
|
||||
// Emit v1 JSON + metadata.extern_c を注入し、v0互換に変換
|
||||
emit_v1_compat_from_ast_with_meta(ast_json, prefer_cfg, externs_json) {
|
||||
local j1 = PipelineV2.lower_stage1_to_mir_v1_with_meta(ast_json, prefer_cfg, externs_json)
|
||||
return MirJsonV1Adapter.to_v0(j1)
|
||||
}
|
||||
|
||||
// No-op entry(箱ガード用)
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ using "lang/src/compiler/pipeline_v2/stage1_name_args_normalizer_box.hako" as Na
|
||||
using "lang/src/compiler/pipeline_v2/alias_preflight_box.hako" as AliasPreflightBox
|
||||
using "lang/src/compiler/pipeline_v2/stage1_args_parser_box.hako" as Stage1ArgsParserBox
|
||||
using "lang/src/compiler/pipeline_v2/pipeline_helpers_box.hako" as PipelineHelpersBox
|
||||
using "lang/src/shared/json/mir_v1_meta_inject_box.hako" as MirV1MetaInjectBox
|
||||
|
||||
flow PipelineV2 {
|
||||
lower_stage1_to_mir(ast_json, prefer_cfg) {
|
||||
@ -150,6 +151,14 @@ flow PipelineV2 {
|
||||
return PipelineV2.lower_stage1_to_mir(ast_json, prefer_cfg)
|
||||
}
|
||||
|
||||
// Experimental (with metadata): emit JSON v1 and inject metadata.extern_c.
|
||||
// externs_json: JSON array text like
|
||||
// [{"func":"Name/Arity","symbol":"c_symbol"}, ...]
|
||||
lower_stage1_to_mir_v1_with_meta(ast_json, prefer_cfg, externs_json) {
|
||||
local j1 = PipelineV2.lower_stage1_to_mir_v1(ast_json, prefer_cfg)
|
||||
return MirV1MetaInjectBox.inject_meta_externs(j1, externs_json)
|
||||
}
|
||||
|
||||
// Experimental helper: emit v1 then downgrade to v0 for Mini‑VM exec
|
||||
lower_stage1_to_mir_v1_compat(ast_json, prefer_cfg) {
|
||||
local j1 = PipelineV2.lower_stage1_to_mir_v1(ast_json, prefer_cfg)
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
using "lang/src/compiler/pipeline_v2/emit_call_box.hako" as EmitCallBox
|
||||
using "lang/src/compiler/pipeline_v2/local_ssa_box.hako" as LocalSSABox
|
||||
using "lang/src/shared/json/mir_v1_meta_inject_box.hako" as MirV1MetaInjectBox
|
||||
|
||||
static box PipelineEmitBox {
|
||||
// Emit Call(name, int-args) → JSON v0, wrapped with LocalSSA ensures
|
||||
@ -10,6 +11,14 @@ static box PipelineEmitBox {
|
||||
local j = EmitCallBox.emit_call_int_args(name, args)
|
||||
return LocalSSABox.ensure_calls(LocalSSABox.ensure_cond(j))
|
||||
}
|
||||
|
||||
// Emit Call(name, int-args) → JSON v1 with metadata.extern_c (if provided)
|
||||
// externs_json: JSON array text like
|
||||
// [{"func":"Name/Arity","symbol":"c_symbol"}, ...]
|
||||
emit_call_int_args_v1_with_meta(name, args, externs_json) {
|
||||
local jv0 = me.emit_call_int_args_v0(name, args)
|
||||
return MirV1MetaInjectBox.inject_meta_externs(jv0, externs_json)
|
||||
}
|
||||
}
|
||||
|
||||
static box PipelineEmitMain { main(args) { return 0 } }
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
using "lang/src/compiler/stage1/json_program_box.hako" as JsonProg
|
||||
|
||||
static box EmitterBox {
|
||||
emit_program(json, usings_json) {
|
||||
emit_program(json, usings_json, externs_json) {
|
||||
if json == null { return json }
|
||||
return JsonProg.normalize(json, usings_json)
|
||||
return JsonProg.normalize(json, usings_json, externs_json)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,21 +5,24 @@ using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/json/json_utils.hako" as JsonUtilsBox
|
||||
|
||||
static box JsonProgramBox {
|
||||
normalize(json, usings_json) {
|
||||
normalize(json, usings_json, externs_json) {
|
||||
local normalized = me.normalize_program(json)
|
||||
// 一括正規化: 配列フィールドの null を [] に丸める(Loop.body / If.then/else / Call.args)
|
||||
normalized = me.fix_null_arrays(normalized)
|
||||
normalized = me.compact_array_ws(normalized)
|
||||
return me.ensure_meta(normalized, usings_json)
|
||||
return me.ensure_meta(normalized, usings_json, externs_json)
|
||||
}
|
||||
|
||||
ensure_meta(json, usings_json) {
|
||||
ensure_meta(json, usings_json, externs_json) {
|
||||
local payload = usings_json
|
||||
if payload == null { payload = "[]" }
|
||||
if payload.size() == 0 { payload = "[]" }
|
||||
local ext = externs_json
|
||||
if ext == null { ext = "[]" }
|
||||
if ext.size() == 0 { ext = "[]" }
|
||||
|
||||
if json == null {
|
||||
return "{\"version\":0,\"kind\":\"Program\",\"body\":[],\"meta\":{\"usings\":" + payload + "}}"
|
||||
return "{\"version\":0,\"kind\":\"Program\",\"body\":[],\"meta\":{\"usings\":" + payload + ",\"extern_c\":" + ext + "}}"
|
||||
}
|
||||
|
||||
local n = json.lastIndexOf("}")
|
||||
@ -33,7 +36,7 @@ static box JsonProgramBox {
|
||||
if last == "{" || last == "," { needs_comma = 0 }
|
||||
}
|
||||
if needs_comma == 1 { head = head + "," }
|
||||
return head + "\"meta\":{\"usings\":" + payload + "}" + tail
|
||||
return head + "\"meta\":{\"usings\":" + payload + ",\"extern_c\":" + ext + "}" + tail
|
||||
}
|
||||
|
||||
normalize_program(json) {
|
||||
|
||||
256
lang/src/externs/normalize/core_extern_normalize.hako
Normal file
256
lang/src/externs/normalize/core_extern_normalize.hako
Normal file
@ -0,0 +1,256 @@
|
||||
// CoreExternNormalize — Phase 20.26 scaffold
|
||||
// Responsibility: provide a stable entry to normalize MIR(JSON v0)
|
||||
// from Method/ModuleFunction forms to Extern names. MVP is a no-op
|
||||
// placeholder so routing can be tested safely.
|
||||
|
||||
using "lang/src/vm/core/json_v0_reader.hako" as NyVmJsonV0Reader
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box CoreExternNormalize {
|
||||
// Normalize entire MIR(JSON v0): ensure entry per function and rewrite
|
||||
// all blocks' instructions for selected String methods → Extern calls.
|
||||
normalize_json(j) {
|
||||
// Find functions array
|
||||
local p_funcs = JsonCursorBox.find_key_dual(j, "\"functions\":[", r#"\"functions\":\["#, 0)
|
||||
if p_funcs < 0 { return j }
|
||||
local lb_funcs = j.indexOf("[", p_funcs)
|
||||
if lb_funcs < 0 { return j }
|
||||
local rb_funcs = JsonCursorBox.seek_array_end(j, lb_funcs)
|
||||
if rb_funcs < 0 { return j }
|
||||
|
||||
// Iterate function objects inside functions array
|
||||
local arr = j.substring(lb_funcs+1, rb_funcs)
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
// skip whitespace/commas
|
||||
loop(true) {
|
||||
if pos >= arr.size() { break }
|
||||
local ch = arr.substring(pos,pos+1)
|
||||
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { pos = pos + 1 continue }
|
||||
break
|
||||
}
|
||||
if pos >= arr.size() { break }
|
||||
if arr.substring(pos,pos+1) != "{" { break }
|
||||
local end = JsonCursorBox.seek_obj_end(arr, pos)
|
||||
if end < 0 { break }
|
||||
local f = arr.substring(pos, end+1)
|
||||
local f2 = me._rewrite_function_json(f)
|
||||
out.push(f2)
|
||||
pos = end + 1
|
||||
}
|
||||
// Join functions
|
||||
local n = out.size(); local i=0; local joined=""
|
||||
loop(i<n) { joined = joined + out.get(i); if i<n-1 { joined = joined + ",\n " }; i=i+1 }
|
||||
// Splice back
|
||||
return j.substring(0, lb_funcs+1) + joined + j.substring(rb_funcs, j.size())
|
||||
}
|
||||
|
||||
_rewrite_function_json(f) {
|
||||
// Insert missing entry (derive from first block id)
|
||||
local f2 = f
|
||||
if NyVmJsonV0Reader.read_entry_id(f2) < 0 {
|
||||
local fb = NyVmJsonV0Reader.first_block(f2)
|
||||
if fb != "" {
|
||||
local eid = NyVmJsonV0Reader.read_block_id(fb)
|
||||
if eid >= 0 {
|
||||
local name_pos = f2.indexOf("\"name\":")
|
||||
if name_pos >= 0 {
|
||||
local colon = f2.indexOf(":", name_pos)
|
||||
if colon >= 0 {
|
||||
local q = colon + 1
|
||||
loop(true) { local ch=f2.substring(q,q+1) if ch==" "||ch=="\n"||ch=="\r"||ch=="\t" { q=q+1 continue } break }
|
||||
if f2.substring(q,q+1) == "\"" {
|
||||
local qend = JsonCursorBox.scan_string_end(f2, q)
|
||||
if qend >= 0 {
|
||||
local prefix = f2.substring(0, qend+1)
|
||||
local suffix = f2.substring(qend+1, f2.size())
|
||||
local insert = ", \"entry\": " + ("" + eid)
|
||||
f2 = prefix + insert + suffix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite all blocks' instructions
|
||||
local p_b = JsonCursorBox.find_key_dual(f2, "\"blocks\":[", r#"\"blocks\":\["#, 0)
|
||||
if p_b < 0 { return f2 }
|
||||
local lb_b = f2.indexOf("[", p_b)
|
||||
if lb_b < 0 { return f2 }
|
||||
local rb_b = JsonCursorBox.seek_array_end(f2, lb_b)
|
||||
if rb_b < 0 { return f2 }
|
||||
local blocks = f2.substring(lb_b+1, rb_b)
|
||||
local bout = new ArrayBox()
|
||||
local bp = 0
|
||||
loop(true) {
|
||||
// skip ws/commas
|
||||
loop(true) {
|
||||
if bp >= blocks.size() { break }
|
||||
local ch = blocks.substring(bp,bp+1)
|
||||
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { bp = bp + 1 continue }
|
||||
break
|
||||
}
|
||||
if bp >= blocks.size() { break }
|
||||
if blocks.substring(bp,bp+1) != "{" { break }
|
||||
local be = JsonCursorBox.seek_obj_end(blocks, bp)
|
||||
if be < 0 { break }
|
||||
local blk = blocks.substring(bp, be+1)
|
||||
local blk2 = me._rewrite_block_json(blk)
|
||||
bout.push(blk2)
|
||||
bp = be + 1
|
||||
}
|
||||
local nb = bout.size(); local bi=0; local bjoined=""
|
||||
loop(bi<nb) { bjoined=bjoined + bout.get(bi); if bi<nb-1 { bjoined=bjoined + ",\n " }; bi=bi+1 }
|
||||
return f2.substring(0, lb_b+1) + bjoined + f2.substring(rb_b, f2.size())
|
||||
}
|
||||
|
||||
_rewrite_block_json(blk) {
|
||||
local p_i = JsonCursorBox.find_key_dual(blk, "\"instructions\":[", r#"\"instructions\":\["#, 0)
|
||||
if p_i < 0 { return blk }
|
||||
local lb_i = blk.indexOf("[", p_i)
|
||||
if lb_i < 0 { return blk }
|
||||
local rb_i = JsonCursorBox.seek_array_end(blk, lb_i)
|
||||
if rb_i < 0 { return blk }
|
||||
local insts = blk.substring(lb_i+1, rb_i)
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local it = NyVmJsonV0Reader.next_instruction(insts, pos)
|
||||
local obj = it.get("obj")
|
||||
if obj == null { break }
|
||||
pos = it.get("next")
|
||||
out.push(me._map_string_calls(obj))
|
||||
}
|
||||
// Join back
|
||||
local n = out.size(); local idx = 0; local joined = ""
|
||||
loop(idx < n) { joined = joined + out.get(idx); if idx < n-1 { joined = joined + ",\n" }; idx = idx + 1 }
|
||||
return blk.substring(0, lb_i+1) + joined + blk.substring(rb_i, blk.size())
|
||||
}
|
||||
|
||||
_map_string_calls(obj) {
|
||||
local rep = obj
|
||||
if obj.indexOf("\"op\":\"mir_call\"") < 0 { return rep }
|
||||
local flags = me._flags_fragment(obj)
|
||||
// String.length aliases
|
||||
if obj.indexOf("\"method\":\"length\"") >= 0 || obj.indexOf("\"method\":\"len\"") >= 0 || obj.indexOf("\"method\":\"size\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst")
|
||||
if recv != "" && dst != "" {
|
||||
return me._build_mir_call(dst, "nyrt.string.length", "[" + recv + "]", flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
// String.substring(recv,start,end)
|
||||
if obj.indexOf("\"method\":\"substring\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst"); local args = me._read_args_digits(obj)
|
||||
if recv != "" && dst != "" && args.size() >= 2 {
|
||||
local a0 = args.get(0); local a1 = args.get(1)
|
||||
return me._build_mir_call(dst, "nyrt.string.substring", "[" + recv + "," + a0 + "," + a1 + "]", flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
// String.indexOf/ find (recv, needle[, from])
|
||||
if obj.indexOf("\"method\":\"indexOf\"") >= 0 || obj.indexOf("\"method\":\"find\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst"); local args = me._read_args_digits(obj)
|
||||
if recv != "" && dst != "" && args.size() >= 1 {
|
||||
local arg_str = "[" + recv + "," + args.get(0)
|
||||
if args.size() >= 2 { arg_str = arg_str + "," + args.get(1) }
|
||||
arg_str = arg_str + "]"
|
||||
return me._build_mir_call(dst, "nyrt.string.indexOf", arg_str, flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
// String.lastIndexOf(recv, needle[, from])
|
||||
if obj.indexOf("\"method\":\"lastIndexOf\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst"); local args = me._read_args_digits(obj)
|
||||
if recv != "" && dst != "" && args.size() >= 1 {
|
||||
local arg_str = "[" + recv + "," + args.get(0)
|
||||
if args.size() >= 2 { arg_str = arg_str + "," + args.get(1) }
|
||||
arg_str = arg_str + "]"
|
||||
return me._build_mir_call(dst, "nyrt.string.lastIndexOf", arg_str, flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
// String.replace(recv, needle)
|
||||
if obj.indexOf("\"method\":\"replace\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst"); local args = me._read_args_digits(obj)
|
||||
if recv != "" && dst != "" && args.size() >= 2 {
|
||||
local a0 = args.get(0); local a1 = args.get(1)
|
||||
return me._build_mir_call(dst, "nyrt.string.replace", "[" + recv + "," + a0 + "," + a1 + "]", flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
// String.charAt(recv, idx)
|
||||
if obj.indexOf("\"method\":\"charAt\"") >= 0 {
|
||||
local recv = me._read_digits(obj, "receiver"); local dst = me._read_digits(obj, "dst"); local args = me._read_args_digits(obj)
|
||||
if recv != "" && dst != "" && args.size() >= 1 {
|
||||
local a0 = args.get(0)
|
||||
return me._build_mir_call(dst, "nyrt.string.charAt", "[" + recv + "," + a0 + "]", flags)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
_flags_fragment(json) {
|
||||
if json.indexOf("\"flags\":") < 0 { return "" }
|
||||
if json.indexOf("\"optionality\":\"bang\"") >= 0 {
|
||||
return ",\\\"flags\\\":{\\\"optionality\\\":\\\"bang\\\"}"
|
||||
}
|
||||
if json.indexOf("\"optionality\":\"optional\"") >= 0 {
|
||||
return ",\\\"flags\\\":{\\\"optionality\\\":\\\"optional\\\"}"
|
||||
}
|
||||
// flags present but null/default → omit
|
||||
return ""
|
||||
}
|
||||
|
||||
_build_mir_call(dst, extern_name, args_json, flags_fragment) {
|
||||
local rep = "{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":" + dst + ",\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"" + extern_name + "\\\"},\\\"args\\\":" + args_json
|
||||
if flags_fragment != "" { rep = rep + flags_fragment }
|
||||
rep = rep + "}}"
|
||||
return rep
|
||||
}
|
||||
|
||||
_read_digits(json, key) {
|
||||
local p = JsonCursorBox.find_key_dual(json, "\""+key+"\":", r#"\""+key+"\":"#, 0)
|
||||
if p < 0 { return "" }
|
||||
local colon = json.indexOf(":", p)
|
||||
if colon < 0 { return "" }
|
||||
local ds = JsonCursorBox.digits_from(json, colon+1)
|
||||
return ds
|
||||
}
|
||||
|
||||
// Read args array as digits strings (VIDs). Returns ArrayBox of strings.
|
||||
_read_args_digits(json) {
|
||||
local out = new ArrayBox()
|
||||
local p = JsonCursorBox.find_key_dual(json, "\"args\":[", r#"\"args\":\["#, 0)
|
||||
if p < 0 { return out }
|
||||
local lb = json.indexOf("[", p)
|
||||
if lb < 0 { return out }
|
||||
local i = lb + 1
|
||||
loop(true) {
|
||||
i = me._skip_ws(json, i)
|
||||
if i >= json.size() { break }
|
||||
local ch = json.substring(i,i+1)
|
||||
if ch == "]" { break }
|
||||
local ds = JsonCursorBox.digits_from(json, i)
|
||||
if ds == "" { break }
|
||||
out.push(ds)
|
||||
i = i + ds.size()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_skip_ws(json, pos) {
|
||||
local i = pos
|
||||
local n = json.size()
|
||||
loop(i < n) {
|
||||
local ch = json.substring(i,i+1)
|
||||
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { i = i + 1 continue }
|
||||
break
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
10
lang/src/llvm_ir/LAYER_GUARD.hako
Normal file
10
lang/src/llvm_ir/LAYER_GUARD.hako
Normal file
@ -0,0 +1,10 @@
|
||||
// LAYER_GUARD — このフォルダは「IR 構築のみ」を担当します
|
||||
// 禁止: リンク/実行/Extern 直接呼び出し/ファイルI/O の実装
|
||||
// 許可: 型・モジュール・関数・基本ブロック・命令の“形”の生成
|
||||
|
||||
static box LLVM_IR_LAYER_GUARD {
|
||||
name(){ return "llvm_ir" }
|
||||
allowed(){ return ["types","module","function","builder","emit"] }
|
||||
forbidden(){ return ["runtime","parser","resolver","linker","run"] }
|
||||
}
|
||||
|
||||
24
lang/src/llvm_ir/README.md
Normal file
24
lang/src/llvm_ir/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# LLVM Script Builder (opt-in, Phase 20.11)
|
||||
|
||||
目的
|
||||
- Python llvmlite ハーネスで行っている IR 構築を、Hakorune スクリプトの薄い箱で段階的に置き換える。
|
||||
- 責務は「IR 構築」に限定し、リンクおよび実行は小ライブラリ(libhako_aot)/AotBox に委譲する。
|
||||
|
||||
ゲート
|
||||
- HAKO_LLVM_SCRIPT_BUILDER=1 で有効化(既定OFF)
|
||||
- 厳格化(未実装はFAIL): HAKO_LLVM_SCRIPT_BUILDER_STRICT=1(既定はFAIL推奨)
|
||||
|
||||
責務境界(Box)
|
||||
- LLVMModuleBox: モジュール作成・型/レイアウト設定・関数登録
|
||||
- LLVMFunctionBox: 関数定義・基本ブロック追加
|
||||
- LLVMBuilderBox: 命令構築(v0: const/binop/ret から開始)
|
||||
- LLVMTypesBox: 代表的なプリミティブ型クエリ
|
||||
- LLVMEmitBox: オブジェクト出力(当面は AotBox へ委譲予定)
|
||||
|
||||
Fail‑Fast
|
||||
- 未実装/未対応は `UNSUPPORTED: <op>` を短文で出力して負値を返す(将来は統一エラーへ)。
|
||||
|
||||
将来拡張
|
||||
- v1: compare/branch/phi、v2: call/extern(hako_* の C-ABI のみ)
|
||||
- MIR→IR の対応は SSOT に集約し、Builder は小さな純関数にまとめる。
|
||||
|
||||
252
lang/src/llvm_ir/boxes/aot_facade.hako
Normal file
252
lang/src/llvm_ir/boxes/aot_facade.hako
Normal file
@ -0,0 +1,252 @@
|
||||
// LLVMAotFacadeBox — IR 文字列(JSON v0)をファイルに書き出し、AotBox で compile/link する薄い委譲層
|
||||
using "lang/src/llvm_ir/boxes/builder.hako" as LLVMBuilderBox
|
||||
// Note: Convenience wrappers build JSON inline to avoid nested resolver issues
|
||||
|
||||
static box LLVMAotFacadeBox {
|
||||
_truthy(s){ if !s { return 0 } local l = s.toLowerCase(); return (l=="1"||l=="true"||l=="on"||l=="yes") }
|
||||
_route(){
|
||||
// Decide route by env (read-only). Default = lib (via AotBox)
|
||||
// HAKO_AOT_USE_FFI=1 → ffi, HAKO_AOT_USE_PLUGIN=1 → plugin, else lib
|
||||
local ffi = call("env.local.get/1", "HAKO_AOT_USE_FFI"); if LLVMAotFacadeBox._truthy(ffi) { return "ffi" }
|
||||
local plug = call("env.local.get/1", "HAKO_AOT_USE_PLUGIN"); if LLVMAotFacadeBox._truthy(plug) { return "plugin" }
|
||||
return "lib"
|
||||
}
|
||||
_q(s){ return "\"" + s + "\"" }
|
||||
_i(n){ return "" + n }
|
||||
_inst_const(dst, val){
|
||||
return "{\"op\":\"const\",\"dst\":" + me._i(dst) + ",\"value\":{\"type\":\"i64\",\"value\":" + me._i(val) + "}}"
|
||||
}
|
||||
_inst_ret(val){ return "{\"op\":\"ret\",\"value\":" + me._i(val) + "}" }
|
||||
_map_binop_kind(opk){
|
||||
if opk == "+" { return "Add" }
|
||||
if opk == "-" { return "Sub" }
|
||||
if opk == "*" { return "Mul" }
|
||||
if opk == "/" { return "Div" }
|
||||
if opk == "%" { return "Mod" }
|
||||
return opk
|
||||
}
|
||||
_inst_binop(kind, lhs, rhs, dst){
|
||||
return "{\"op\":\"binop\",\"op_kind\":" + me._q(kind) + ",\"lhs\":" + me._i(lhs) + ",\"rhs\":" + me._i(rhs) + ",\"dst\":" + me._i(dst) + "}"
|
||||
}
|
||||
_wrap_fn(body_json){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" + body_json + "] } ] } ] }"
|
||||
}
|
||||
compile_link_json(json_text, obj_out, exe_out, flags){
|
||||
// Fail‑Fast: validate args
|
||||
if !json_text || !obj_out || !exe_out { return -1 }
|
||||
// Write JSON to a temp file next to obj_out
|
||||
local fb = new FileBox()
|
||||
local json_path = obj_out + ".json"
|
||||
// Ensure file is created and truncated
|
||||
fb.open(json_path, "w")
|
||||
fb.write(json_text)
|
||||
fb.close()
|
||||
// Delegate to AotBox (route is read-only; actual path is handled by C‑ABI/env)
|
||||
local _r = LLVMAotFacadeBox._route()
|
||||
local a = new AotBox()
|
||||
local rc1 = a.compile(json_path, obj_out)
|
||||
if rc1 != 0 { return rc1 }
|
||||
local rc2 = a.link(obj_out, exe_out, flags ? flags : "")
|
||||
return rc2
|
||||
}
|
||||
|
||||
// Convenience wrappers (delegate to LLVMBuilderBox when gate=on; else inline JSON)
|
||||
compile_link_ret0(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) {
|
||||
local json = LLVMBuilderBox.program_ret0()
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// Inline JSON fallback to avoid nested using resolver dependencies
|
||||
local body = me._inst_const(1, 0) + "," + me._inst_ret(1)
|
||||
local json = me._wrap_fn(body)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
compile_link_ret_i64(v, obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) {
|
||||
local json = LLVMBuilderBox.program_ret_i64(v)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
local body = me._inst_const(1, v) + "," + me._inst_ret(1)
|
||||
local json = me._wrap_fn(body)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
compile_link_binop_i64(lhs, rhs, opk, obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) {
|
||||
local json = LLVMBuilderBox.program_binop_i64(lhs, rhs, opk)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// Inline JSON fallback
|
||||
local kind = me._map_binop_kind(opk)
|
||||
local body = me._inst_const(1, lhs) + "," + me._inst_const(2, rhs) + "," + me._inst_binop(kind, 1, 2, 3) + "," + me._inst_ret(3)
|
||||
local json = me._wrap_fn(body)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
|
||||
// v1(compare minimal) — emit a compare (Eq) but return 0 to keep exe exit code stable
|
||||
// This exercises the compare path in AOT while ensuring the process exit is 0.
|
||||
compile_link_compare_eq_i64(lhs, rhs, obj_out, exe_out, flags){
|
||||
// Build: const 1=lhs, const 2=rhs, compare(dst=3, operation==, lhs=1, rhs=2),
|
||||
// then const 4=0, binop(dst=5, op="*", lhs=3, rhs=4), ret 5
|
||||
local inst_c1 = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + me._i(lhs) + "}}"
|
||||
local inst_c2 = "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + me._i(rhs) + "}}"
|
||||
local inst_cmp = "{\"op\":\"compare\",\"dst\":3,\"operation\":\"==\",\"lhs\":1,\"rhs\":2}"
|
||||
local inst_c0 = "{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":0}}"
|
||||
local inst_mul = "{\"op\":\"binop\",\"dst\":5,\"operation\":\"*\",\"lhs\":3,\"rhs\":4}"
|
||||
local inst_ret = "{\"op\":\"ret\",\"value\":5}"
|
||||
local body = inst_c1 + "," + inst_c2 + "," + inst_cmp + "," + inst_c0 + "," + inst_mul + "," + inst_ret
|
||||
local json = me._wrap_fn(body)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
|
||||
// v1(compare+branch minimal) — generate multi-block JSON with conditional branch; both paths return 0
|
||||
compile_link_compare_branch_i64(lhs, rhs, opk, obj_out, exe_out, flags){
|
||||
local op = opk ? opk : ">"
|
||||
// blocks: 0=cmp+branch, 1=then(ret 0), 2=else(ret 0)
|
||||
local b0 = "{\"id\":0,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + me._i(lhs) + "}}," +
|
||||
"{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + me._i(rhs) + "}}," +
|
||||
"{\"op\":\"compare\",\"dst\":3,\"operation\":" + me._q(op) + ",\"lhs\":1,\"rhs\":2}," +
|
||||
"{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}] }"
|
||||
local b1 = "{\"id\":1,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":4}] }"
|
||||
local b2 = "{\"id\":2,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":5}] }"
|
||||
local json = "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [" + b0 + "," + b1 + "," + b2 + "] } ] }"
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
|
||||
// v1(phi-if minimal) — diamond shape with PHI at merge; returns 0 to keep exit code stable
|
||||
compile_link_phi_if_i64(val_then, val_else, obj_out, exe_out, flags){
|
||||
// 0: jump->1 always via const(true) compare; 1: const then; 2: const else; 3: phi merge; ret 0
|
||||
local b0 = "{\"id\":0,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":1}}," +
|
||||
"{\"op\":\"compare\",\"dst\":2,\"operation\":\"==\",\"lhs\":1,\"rhs\":1}," +
|
||||
"{\"op\":\"branch\",\"cond\":2,\"then\":1,\"else\":2}] }"
|
||||
local b1 = "{\"id\":1,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":" + me._i(val_then) + "}}," +
|
||||
"{\"op\":\"jump\",\"target\":3}] }"
|
||||
local b2 = "{\"id\":2,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + me._i(val_else) + "}}," +
|
||||
"{\"op\":\"jump\",\"target\":3}] }"
|
||||
local b3 = "{\"id\":3,\"instructions\":[" +
|
||||
"{\"op\":\"phi\",\"dst\":5,\"type\":\"i64\",\"incoming\":[{\"block\":1,\"value\":3},{\"block\":2,\"value\":4}]}," +
|
||||
"{\"op\":\"const\",\"dst\":6,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":6}] }"
|
||||
local json = "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [" + b0 + "," + b1 + "," + b2 + "," + b3 + "] } ] }"
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
|
||||
// extern call convenience wrappers (console.*) — build via Builder and link
|
||||
compile_link_call_console_log(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_console_log_ret0() }
|
||||
else {
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"string\",\"value\":\"hello\"}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.console.log\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
compile_link_call_console_warn(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_console_warn_ret0() }
|
||||
else {
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"string\",\"value\":\"warn-message\"}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.console.warn\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
compile_link_call_console_error(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_console_error_ret0() }
|
||||
else {
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"string\",\"value\":\"error-message\"}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":\"null\",\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.console.error\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// Negative helper for smoke: invalid extern name to observe Fail‑Fast
|
||||
compile_link_call_console_invalid(obj_out, exe_out, flags){
|
||||
// Construct minimal JSON inline to avoid depending on Builder for invalid case
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"string\",\"value\":\"hello\"}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.console.nope\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
local json = me._wrap_fn(body)
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// time.now_ms — build via Builder when gate, else inline JSON; ret 0
|
||||
compile_link_call_time_now_ms(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_time_now_ms_ret0() }
|
||||
else {
|
||||
local body = "{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.time.now_ms\"},\"args\":[],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// JSON.stringify(any) — via nyash.json.stringify_h; ret 0
|
||||
compile_link_call_json_stringify(obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_json_stringify_ret0() }
|
||||
else {
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":42}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"nyash.json.stringify_h\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// env.mem.alloc/free wrapper — ret 0
|
||||
compile_link_call_mem_alloc_free(sz, obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_mem_alloc_free_ret0(sz) }
|
||||
else {
|
||||
local s = sz ? sz : 16
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + me._i(s) + "}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.mem.alloc\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":3,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.mem.free\"},\"args\":[2],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":4}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
// env.local.get wrapper — ret 0 (value ignored)
|
||||
compile_link_call_env_local_get(key, obj_out, exe_out, flags){
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
local json
|
||||
if on && (LLVMAotFacadeBox._truthy(on)) { json = LLVMBuilderBox.program_call_env_local_get_ret0(key) }
|
||||
else {
|
||||
local k = key ? key : "SMOKES_ENV_LOCAL_GET"
|
||||
local body = "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"string\",\"value\":\"" + k + "\"}}," +
|
||||
"{\"op\":\"mir_call\",\"dst\":2,\"mir_call\":{\"callee\":{\"type\":\"Extern\",\"name\":\"env.local.get\"},\"args\":[1],\"effects\":[]}}," +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":3}"
|
||||
json = me._wrap_fn(body)
|
||||
}
|
||||
return LLVMAotFacadeBox.compile_link_json(json, obj_out, exe_out, flags)
|
||||
}
|
||||
}
|
||||
162
lang/src/llvm_ir/boxes/aot_prep.hako
Normal file
162
lang/src/llvm_ir/boxes/aot_prep.hako
Normal file
@ -0,0 +1,162 @@
|
||||
// aot_prep.hako — AotPrepBox (pre-MIR normalizer/optimizer; skeleton)
|
||||
// 入出力(最小仕様)
|
||||
// - AotPrepBox.prep/1(json_in_path: String) -> String (json_out_path)
|
||||
// 責務
|
||||
// - JSON(MIR v0) の軽量正規化(キー順/冗長キー削除)と安全な const/binop(+,-,*)/ret の単一ブロック畳み込み
|
||||
// - 既定ではパススルー(Rust 側 maybe_prepare_mir_json が実体)。段階的にこちらへ移管する
|
||||
|
||||
using "lang/src/shared/mir/mir_io_box.hako" as MirIoBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box AotPrepBox {
|
||||
// AotPrepBox.prep
|
||||
// 入力: JSONファイルパス(MIR v0)
|
||||
// 出力: 正規化後のJSONを書き出したパス(<in>.prep.json)。失敗時は入力パスを返す(Fail‑FastはRust側が継続)。
|
||||
prep(json_in_path) {
|
||||
if !json_in_path { return json_in_path }
|
||||
// Read input
|
||||
local fb = new FileBox()
|
||||
fb.open(json_in_path, "r")
|
||||
local src = fb.read()
|
||||
fb.close()
|
||||
if !src { return json_in_path }
|
||||
|
||||
// Phase‑1: 文字列正規化(安定化)
|
||||
// いまは canonicalize は恒等(将来はHostBridgeでキー順安定化)
|
||||
local canon = MirIoBox.normalize(src)
|
||||
|
||||
// Phase‑2: 安全な単一ブロック const/binop(+,-,*)/ret の畳み込み(最小実装)
|
||||
// 備考: まずは main 関数を優先対象とし、成立時のみ最小 JSON に置換(今後は in‑place 置換へ段階移行)。
|
||||
local out = AotPrepBox._try_fold_const_binop_ret(canon)
|
||||
if !out { out = canon }
|
||||
|
||||
// Decide output path
|
||||
local out_path = json_in_path + ".prep.json"
|
||||
fb.open(out_path, "w")
|
||||
fb.write(out)
|
||||
fb.close()
|
||||
return out_path
|
||||
}
|
||||
|
||||
// 内部: 最小の安全畳み込み(JSON文字列ベース)
|
||||
_try_fold_const_binop_ret(json) {
|
||||
if !json { return null }
|
||||
// Helper: find the [ ... ] span of the first block's instructions for the function near `start_from`.
|
||||
local find_instr_span_from = fun(s, start_from) {
|
||||
local key = "\"instructions\":["
|
||||
local pos = s.indexOf(key, start_from)
|
||||
if pos < 0 { return [-1, -1] }
|
||||
local ls = s.indexOf("[", pos)
|
||||
if ls < 0 { return [-1, -1] }
|
||||
local depth = 0
|
||||
local i = ls
|
||||
local L = s.size()
|
||||
local rs = -1
|
||||
loop(i < L) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "[" { depth = depth + 1 }
|
||||
if ch == "]" { depth = depth - 1; if depth == 0 { rs = i; break } }
|
||||
i = i + 1
|
||||
}
|
||||
return [ls, rs]
|
||||
}
|
||||
// Helper: attempt to fold within a given [arr_start, arr_end] span; return replaced JSON on success
|
||||
local try_fold_in_span = fun(s, arr_start, arr_end) {
|
||||
if arr_start < 0 || arr_end < 0 { return null }
|
||||
local body = s.substring(arr_start, arr_end+1)
|
||||
// Need two const, a binop, and a ret in this span
|
||||
local p1 = body.indexOf("\"op\":\"const\"")
|
||||
local p2 = body.indexOf("\"op\":\"const\"", (p1>=0 ? (p1+1) : 0))
|
||||
local pb = body.indexOf("\"op\":\"binop\"", (p2>=0 ? (p2+1) : 0))
|
||||
local pr = body.indexOf("\"op\":\"ret\"", (pb>=0 ? (pb+1) : 0))
|
||||
if p1 < 0 || p2 < 0 || pb < 0 || pr < 0 { return null }
|
||||
// parse helpers within body
|
||||
local parse_dst = fun(ss, pos) {
|
||||
local k = "\"dst\":"
|
||||
local i = ss.indexOf(k, pos)
|
||||
if i < 0 { return -1 }
|
||||
i = i + k.size()
|
||||
local digs = StringHelpers.read_digits(ss, i)
|
||||
if digs == "" { return -1 }
|
||||
return StringHelpers.to_i64(digs)
|
||||
}
|
||||
local parse_val = fun(ss, pos) {
|
||||
local k = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||
local i = ss.indexOf(k, pos)
|
||||
if i < 0 { return null }
|
||||
i = i + k.size()
|
||||
local digs = StringHelpers.read_digits(ss, i)
|
||||
if digs == "" { return null }
|
||||
return StringHelpers.to_i64(digs)
|
||||
}
|
||||
local d1 = parse_dst(body, p1)
|
||||
local a = parse_val(body, p1)
|
||||
local d2 = parse_dst(body, p2)
|
||||
local b = parse_val(body, p2)
|
||||
if d1 < 0 || d2 < 0 || a == null || b == null { return null }
|
||||
local find_num = fun(ss, key, pos) {
|
||||
local k = key
|
||||
local i = ss.indexOf(k, pos)
|
||||
if i < 0 { return -1 }
|
||||
i = i + k.size()
|
||||
local digs = StringHelpers.read_digits(ss, i)
|
||||
if digs == "" { return -1 }
|
||||
return StringHelpers.to_i64(digs)
|
||||
}
|
||||
local find_op = fun(ss, pos) {
|
||||
local k = "\"operation\":\""
|
||||
local i = ss.indexOf(k, pos)
|
||||
if i < 0 { return "" }
|
||||
i = i + k.size()
|
||||
local j = ss.indexOf("\"", i)
|
||||
if j < 0 { return "" }
|
||||
return ss.substring(i, j)
|
||||
}
|
||||
local lhs = find_num(body, "\"lhs\":", pb)
|
||||
local rhs = find_num(body, "\"rhs\":", pb)
|
||||
local bop = find_op(body, pb)
|
||||
local d3 = find_num(body, "\"dst\":", pb)
|
||||
if lhs != d1 || rhs != d2 || d3 < 0 { return null }
|
||||
local rv = find_num(body, "\"value\":", pr)
|
||||
if rv != d3 { return null }
|
||||
// binop allowed: +,-,* only
|
||||
local res = 0
|
||||
if bop == "+" { res = a + b } else { if bop == "-" { res = a - b } else { if bop == "*" { res = a * b } else { return null } } }
|
||||
// build new array and replace in-place
|
||||
local new_insts = "[" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":" + StringHelpers.int_to_str(d1) + ",\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + StringHelpers.int_to_str(res) + "}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":" + StringHelpers.int_to_str(d1) + "}]"
|
||||
local head = s.substring(0, arr_start)
|
||||
local tail = s.substring(arr_end + 1, s.size())
|
||||
return head + new_insts + tail
|
||||
}
|
||||
// Pass 1: prefer name:"main"
|
||||
local fn_pos = json.indexOf("\"name\":\"main\"")
|
||||
if fn_pos >= 0 {
|
||||
local span = find_instr_span_from(json, fn_pos)
|
||||
local ls = span[0]; local rs = span[1]
|
||||
local repl = try_fold_in_span(json, ls, rs)
|
||||
if repl { return repl }
|
||||
}
|
||||
// Pass 2: scan functions sequentially and attempt per function
|
||||
local froot = json.indexOf("\"functions\":[")
|
||||
if froot < 0 { return null }
|
||||
local scan = froot
|
||||
local tries = 0
|
||||
loop(tries < 16) {
|
||||
local np = json.indexOf("\"name\":\"", scan+1)
|
||||
if np < 0 { break }
|
||||
local span2 = find_instr_span_from(json, np)
|
||||
local ls2 = span2[0]; local rs2 = span2[1]
|
||||
if ls2 >= 0 && rs2 >= 0 {
|
||||
local repl2 = try_fold_in_span(json, ls2, rs2)
|
||||
if repl2 { return repl2 }
|
||||
scan = rs2 + 1
|
||||
} else {
|
||||
scan = np + 8
|
||||
}
|
||||
tries = tries + 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
128
lang/src/llvm_ir/boxes/builder.hako
Normal file
128
lang/src/llvm_ir/boxes/builder.hako
Normal file
@ -0,0 +1,128 @@
|
||||
// LLVMBuilderBox — 命令構築(v0: const/binop/ret の骨格)
|
||||
static box LLVMBuilderBox {
|
||||
program_ret0(){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"ret\",\"value\":1}] } ] } ] }"
|
||||
}
|
||||
program_binop_i64(lhs, rhs, opk){
|
||||
local op = opk ? opk : "+"
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs + "}}," +
|
||||
"{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs + "}}," +
|
||||
"{\"op\":\"binop\",\"dst\":3,\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2}," +
|
||||
"{\"op\":\"ret\",\"value\":3}] } ] } ] }"
|
||||
}
|
||||
program_ret_i64(v){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + v + "}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":1}] } ] } ] }"
|
||||
}
|
||||
const_i64(fn_handle, value){
|
||||
local strict = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER_STRICT");
|
||||
if strict && strict != "0" && strict != "false" { print("UNSUPPORTED: const_i64 (stub)"); return -1 }
|
||||
return 0
|
||||
}
|
||||
binop_add(fn_handle, lhs, rhs){
|
||||
local strict = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER_STRICT");
|
||||
if strict && strict != "0" && strict != "false" { print("UNSUPPORTED: binop_add (stub)"); return -1 }
|
||||
return 0
|
||||
}
|
||||
ret(fn_handle, val){
|
||||
local strict = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER_STRICT");
|
||||
if strict && strict != "0" && strict != "false" { print("UNSUPPORTED: ret (stub)"); return -1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
// v1 programs (JSON emitters) — behavior: return JSON string for AOT facade
|
||||
program_compare_branch_ret0(lhs, rhs, opk){
|
||||
local op = opk ? opk : ">"
|
||||
local b0 = "{\"id\":0,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs + "}}," +
|
||||
"{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs + "}}," +
|
||||
"{\"op\":\"compare\",\"dst\":3,\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2}," +
|
||||
"{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}] }"
|
||||
local b1 = "{\"id\":1,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":4}] }"
|
||||
local b2 = "{\"id\":2,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":5}] }"
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [" + b0 + "," + b1 + "," + b2 + "] } ] }"
|
||||
}
|
||||
|
||||
program_phi_if_ret0(val_then, val_else){
|
||||
local b0 = "{\"id\":0,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":1}}," +
|
||||
"{\"op\":\"compare\",\"dst\":2,\"operation\":\"==\",\"lhs\":1,\"rhs\":1}," +
|
||||
"{\"op\":\"branch\",\"cond\":2,\"then\":1,\"else\":2}] }"
|
||||
local b1 = "{\"id\":1,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":" + val_then + "}}," +
|
||||
"{\"op\":\"jump\",\"target\":3}] }"
|
||||
local b2 = "{\"id\":2,\"instructions\":[" +
|
||||
"{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + val_else + "}}," +
|
||||
"{\"op\":\"jump\",\"target\":3}] }"
|
||||
local b3 = "{\"id\":3,\"instructions\":[" +
|
||||
"{\"op\":\"phi\",\"dst\":5,\"type\":\"i64\",\"incoming\":[{\"block\":1,\"value\":3},{\"block\":2,\"value\":4}]}," +
|
||||
"{\"op\":\"const\",\"dst\":6,\"value\":{\"type\":\"i64\",\"value\":0}}," +
|
||||
"{\"op\":\"ret\",\"value\":6}] }"
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [" + b0 + "," + b1 + "," + b2 + "," + b3 + "] } ] }"
|
||||
}
|
||||
|
||||
// v2 extern calls (console.*) — return JSON for AOT facade
|
||||
program_call_console_log_ret0(){
|
||||
// const s="hello"; mir_call Extern(env.console.log) s; ret 0
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"hello\\\"}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.console.log\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
program_call_console_warn_ret0(){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"warn-message\\\"}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.console.warn\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
program_call_console_error_ret0(){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"error-message\\\"}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.console.error\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
// env.mem.alloc/free roundtrip; return 0
|
||||
program_call_mem_alloc_free_ret0(size){
|
||||
local sz = size ? size : 16
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + sz + "}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.mem.alloc\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":3,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.mem.free\\\"},\\\"args\\\":[2],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":4,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":4}] } ] } ] }"
|
||||
}
|
||||
// env.local.get (value unused); return 0
|
||||
program_call_env_local_get_ret0(key){
|
||||
local k = key ? key : "SMOKES_ENV_LOCAL_GET"
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"" + k + "\\\"}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.local.get\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
// v2 extern calls (time.now_ms / JSON.stringify)
|
||||
// Call env.time.now_ms() and return 0 (exe exit code stable)
|
||||
program_call_time_now_ms_ret0(){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"env.time.now_ms\\\"},\\\"args\\\":[],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
// Call JSON.stringify(any) via nyash.json.stringify_h and return 0
|
||||
program_call_json_stringify_ret0(){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":42}}," +
|
||||
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":2,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Extern\\\",\\\"name\\\":\\\"nyash.json.stringify_h\\\"},\\\"args\\\":[1],\\\"effects\\\":[]}}," +
|
||||
"{\\\"op\\\":\\\"const\\\",\\\"dst\\\":3,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":0}}," +
|
||||
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":3}] } ] } ] }"
|
||||
}
|
||||
}
|
||||
9
lang/src/llvm_ir/boxes/emit.hako
Normal file
9
lang/src/llvm_ir/boxes/emit.hako
Normal file
@ -0,0 +1,9 @@
|
||||
// LLVMEmitBox — オブジェクト出力(当面は委譲予定・MVPはFail‑Fast stub)
|
||||
static box LLVMEmitBox {
|
||||
write_object(mod_handle, path){
|
||||
// まだスクリプト内で IR→obj を完結させない方針。委譲前提のためFail‑Fast。
|
||||
print("UNSUPPORTED: write_object (delegate to AotBox/libhako_aot)");
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
10
lang/src/llvm_ir/boxes/function.hako
Normal file
10
lang/src/llvm_ir/boxes/function.hako
Normal file
@ -0,0 +1,10 @@
|
||||
// LLVMFunctionBox — 関数定義と基本ブロック操作(MVPは形のみ)
|
||||
static box LLVMFunctionBox {
|
||||
append_block(fn_handle, name){
|
||||
return 1
|
||||
}
|
||||
set_insert_point(fn_handle, bb_handle){
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
21
lang/src/llvm_ir/boxes/module.hako
Normal file
21
lang/src/llvm_ir/boxes/module.hako
Normal file
@ -0,0 +1,21 @@
|
||||
// LLVMModuleBox — IR 構築の起点(MVPは形のみ)
|
||||
static box LLVMModuleBox {
|
||||
new(name, triple, dl){
|
||||
// Gate: opt‑in のみ
|
||||
local on = call("env.local.get/1", "HAKO_LLVM_SCRIPT_BUILDER");
|
||||
if !on || on == "0" || on == "false" { return -1 }
|
||||
// For MVP, just return a dummy handle (=1)
|
||||
return 1
|
||||
}
|
||||
set_target_triple(handle, triple){
|
||||
return 0
|
||||
}
|
||||
set_data_layout(handle, dl){
|
||||
return 0
|
||||
}
|
||||
add_function(handle, name, ret_ty, args_array){
|
||||
// Return a dummy function handle (=1)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
10
lang/src/llvm_ir/boxes/types.hako
Normal file
10
lang/src/llvm_ir/boxes/types.hako
Normal file
@ -0,0 +1,10 @@
|
||||
// LLVMTypesBox — 代表的なプリミティブ型のクエリ(v0)
|
||||
static box LLVMTypesBox {
|
||||
i1(){ return "i1" }
|
||||
i8(){ return "i8" }
|
||||
i32(){ return "i32" }
|
||||
i64(){ return "i64" }
|
||||
f64(){ return "f64" }
|
||||
ptr(ty){ return ty + "*" }
|
||||
}
|
||||
|
||||
44
lang/src/llvm_ir/boxes/v0_builder.hako
Normal file
44
lang/src/llvm_ir/boxes/v0_builder.hako
Normal file
@ -0,0 +1,44 @@
|
||||
// LLVMV0BuilderBox — v0: 最小 MIR(JSON v0) を直接生成する軽量ビルダ(依存レス)
|
||||
// 目的: スモークでの AOT 経路確認を最小依存で実現する。
|
||||
|
||||
static box LLVMV0BuilderBox {
|
||||
_q(s){ return "\"" + s + "\"" }
|
||||
_i(n){ return "" + n }
|
||||
_inst_const(dst, val){
|
||||
return "{\"op\":\"const\",\"dst\":" + me._i(dst) + ",\"value\":{\"type\":\"i64\",\"value\":" + me._i(val) + "}}"
|
||||
}
|
||||
_inst_ret(val){ return "{\"op\":\"ret\",\"value\":" + me._i(val) + "}" }
|
||||
_map_binop_kind(opk){
|
||||
if opk == "+" { return "Add" }
|
||||
if opk == "-" { return "Sub" }
|
||||
if opk == "*" { return "Mul" }
|
||||
if opk == "/" { return "Div" }
|
||||
if opk == "%" { return "Mod" }
|
||||
return opk
|
||||
}
|
||||
_inst_binop(kind, lhs, rhs, dst){
|
||||
return "{\"op\":\"binop\",\"op_kind\":" + me._q(kind) + ",\"lhs\":" + me._i(lhs) + ",\"rhs\":" + me._i(rhs) + ",\"dst\":" + me._i(dst) + "}"
|
||||
}
|
||||
_wrap_fn(body_json){
|
||||
return "{ \"version\": 0, \"functions\": [ { \"name\": \"main\", \"params\": [], \"blocks\": [ { \"id\": 0, \"instructions\": [" + body_json + "] } ] } ] }"
|
||||
}
|
||||
|
||||
// return 0(v0代表)
|
||||
ret0(){
|
||||
local body = me._inst_const(1, 0) + "," + me._inst_ret(1)
|
||||
return me._wrap_fn(body)
|
||||
}
|
||||
|
||||
// return <v>
|
||||
ret_i64(v){
|
||||
local body = me._inst_const(1, v) + "," + me._inst_ret(1)
|
||||
return me._wrap_fn(body)
|
||||
}
|
||||
|
||||
// (<lhs> <op> <rhs>) → return (op: +|-|*|/|%)
|
||||
binop_i64(lhs, rhs, opk){
|
||||
local kind = me._map_binop_kind(opk)
|
||||
local body = me._inst_const(1, lhs) + "," + me._inst_const(2, rhs) + "," + me._inst_binop(kind, 1, 2, 3) + "," + me._inst_ret(3)
|
||||
return me._wrap_fn(body)
|
||||
}
|
||||
}
|
||||
49
lang/src/llvm_ir/examples/v0_const_binop.hako
Normal file
49
lang/src/llvm_ir/examples/v0_const_binop.hako
Normal file
@ -0,0 +1,49 @@
|
||||
// v0_const_binop.hako — LLVM Script Builder v0(const/binop/ret)デモ
|
||||
// 役割: 最小 MIR(JSON v0) を生成し、AotBox 経由で exe を作る(IR構築は段階導入のためMIRビルダーを使用)。
|
||||
|
||||
using "lang/src/compiler/pipeline_v2/emit_return_box.hako" as EmitReturnBox
|
||||
using "lang/src/compiler/pipeline_v2/emit_binop_box.hako" as EmitBinopBox
|
||||
|
||||
static box V0Demo {
|
||||
// 返り値 0 の exe を生成
|
||||
emit_ret0(obj_out, exe_out) {
|
||||
local json = EmitReturnBox.emit_return_int2(0, 0)
|
||||
return V0Demo._compile_link(json, obj_out, exe_out, "")
|
||||
}
|
||||
// (lhs op rhs) を計算して返す exe を生成(op=+|-|*|/)
|
||||
emit_binop(lhs, rhs, opk, obj_out, exe_out) {
|
||||
local json = EmitBinopBox.emit_binop2(lhs, rhs, opk, 0)
|
||||
return V0Demo._compile_link(json, obj_out, exe_out, "")
|
||||
}
|
||||
_compile_link(json, obj_out, exe_out, flags) {
|
||||
// AotBox プラグイン(libhako_aot)
|
||||
local a = new AotBox()
|
||||
// 書き出し先の JSON を一時ファイル化(AotBoxはパス受け取りのため)
|
||||
// 簡易: FileBox を用いず、言語側のユーティリティがない場合は AotBox に直接渡すため、
|
||||
// ここでは直接は書かない(EmitReturn/EmitBinop は JSON 文字列を返す)。
|
||||
// 便宜上、FileBox で書き出してから AotBox.compile へ渡す。
|
||||
local fb = new FileBox()
|
||||
local json_path = obj_out + ".json"
|
||||
fb.open(json_path)
|
||||
fb.write(json)
|
||||
fb.close()
|
||||
// compile/link
|
||||
local rc1 = a.compile(json_path, obj_out)
|
||||
local rc2 = a.link(obj_out, exe_out, flags)
|
||||
return (rc1 == 0 && rc2 == 0) ? 0 : -1
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
// デモエントリ: ret 0 の exe を生成
|
||||
main(){
|
||||
local tmp = call("env.local.get/1", "NYASH_ROOT")
|
||||
if !tmp { tmp = "." }
|
||||
local obj = tmp + "/tmp/v0_min.o"
|
||||
local exe = tmp + "/tmp/v0_min_exe"
|
||||
local rc = V0Demo.emit_ret0(obj, exe)
|
||||
print(rc)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
14
lang/src/llvm_ir/hako_module.toml
Normal file
14
lang/src/llvm_ir/hako_module.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[module]
|
||||
name = "selfhost.llvm.ir"
|
||||
description = "LLVM Script Builder (IR only; opt-in)"
|
||||
|
||||
[exports]
|
||||
boxes = [
|
||||
"LLVMModuleBox",
|
||||
"LLVMFunctionBox",
|
||||
"LLVMBuilderBox",
|
||||
"LLVMTypesBox",
|
||||
"LLVMEmitBox",
|
||||
"LLVMV0BuilderBox",
|
||||
"LLVMAotFacadeBox",
|
||||
]
|
||||
27
lang/src/mir/externs/ssot_emit.hako
Normal file
27
lang/src/mir/externs/ssot_emit.hako
Normal file
@ -0,0 +1,27 @@
|
||||
// ExternsSSOTEmitter — dev-only SSOT generator (Phase‑20.29)
|
||||
// Purpose: emit a minimal externs SSOT JSON for development.
|
||||
// Notes: This is intentionally small; runtime merges with built-ins.
|
||||
|
||||
static box ExternsSSOTEmitter {
|
||||
emit(){
|
||||
return "[\n"
|
||||
+ " {\"interface\":\"nyrt.array\",\"method\":\"size\",\"effects\":\"read\",\"params\":[\"Box:ArrayBox\"],\"returns\":\"Integer\"},\n"
|
||||
+ " {\"interface\":\"nyrt.array\",\"method\":\"get\",\"effects\":\"read\",\"params\":[\"Box:ArrayBox\",\"Integer\"],\"returns\":\"Unknown\"},\n"
|
||||
+ " {\"interface\":\"nyrt.array\",\"method\":\"set\",\"effects\":\"mut\",\"params\":[\"Box:ArrayBox\",\"Integer\",\"Unknown\"],\"returns\":\"Void\"},\n"
|
||||
+ " {\"interface\":\"nyrt.array\",\"method\":\"pop\",\"effects\":\"mut\",\"params\":[\"Box:ArrayBox\"],\"returns\":\"Unknown\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"length\",\"effects\":\"read\",\"params\":[\"String\"],\"returns\":\"Integer\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"indexOf\",\"effects\":\"read\",\"params\":[\"String\",\"String\",\"Integer\"],\"returns\":\"Integer\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"lastIndexOf\",\"effects\":\"read\",\"params\":[\"String\",\"String\",\"Integer\"],\"returns\":\"Integer\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"substring\",\"effects\":\"read\",\"params\":[\"String\",\"Integer\",\"Integer\"],\"returns\":\"String\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"charAt\",\"effects\":\"read\",\"params\":[\"String\",\"Integer\"],\"returns\":\"String\"},\n"
|
||||
+ " {\"interface\":\"nyrt.string\",\"method\":\"replace\",\"effects\":\"read\",\"params\":[\"String\",\"String\",\"String\"],\"returns\":\"String\"},\n"
|
||||
+ " {\"interface\":\"env.console\",\"method\":\"log\",\"effects\":\"io\",\"params\":[\"String\"],\"returns\":\"Void\"},\n"
|
||||
+ " {\"interface\":\"env.console\",\"method\":\"warn\",\"effects\":\"io\",\"params\":[\"String\"],\"returns\":\"Void\"},\n"
|
||||
+ " {\"interface\":\"env.console\",\"method\":\"error\",\"effects\":\"io\",\"params\":[\"String\"],\"returns\":\"Void\"},\n"
|
||||
+ " {\"interface\":\"env.time\",\"method\":\"now_ms\",\"effects\":\"read\",\"params\":[],\"returns\":\"Integer\"},\n"
|
||||
+ " {\"interface\":\"env.mem\",\"method\":\"name\",\"effects\":\"read\",\"params\":[],\"returns\":\"String\"},\n"
|
||||
+ " {\"interface\":\"nyrt.ops\",\"method\":\"op_eq\",\"effects\":\"read\",\"params\":[\"Unknown\",\"Unknown\"],\"returns\":\"Bool\"}\n"
|
||||
+ "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
114
lang/src/mir/min_emitter.hako
Normal file
114
lang/src/mir/min_emitter.hako
Normal file
@ -0,0 +1,114 @@
|
||||
// min_emitter.hako — Minimal MIR(JSON v0) emitter (const/ret/binop)
|
||||
// Dev/opt‑in helper for P1: Hako‑side JSON generation without touching Rust semantics.
|
||||
|
||||
static box MinMirEmitter {
|
||||
_header() { return "{\n \"kind\": \"MIR\",\n \"schema_version\": \"1.0\",\n \"functions\": [\n {\n \"name\": \"Main.main\", \"entry\": 1,\n \"blocks\": [\n { \"id\": 1, \"instructions\": [\n" }
|
||||
_footer() { return " ] }\n ]\n }\n ]\n}\n" }
|
||||
_const(dst, val) { return " {\"op\":\"const\",\"dst\":" + (""+dst) + ",\"value\":{\"type\":\"i64\",\"value\":" + (""+val) + "}},\n" }
|
||||
_ret(val) { return " {\"op\":\"ret\",\"value\":" + (""+val) + "}\n" }
|
||||
_binop(dst, op, lhs, rhs) {
|
||||
return " {\"op\":\"binop\",\"dst\":" + (""+dst) + ",\"lhs\":" + (""+lhs) + ",\"rhs\":" + (""+rhs) + ",\"operation\":\"" + op + "\"},\n"
|
||||
}
|
||||
|
||||
emit_ret_i64(n) {
|
||||
return me._header() + me._const(1, n) + me._ret(1) + me._footer()
|
||||
}
|
||||
emit_add_i64(a, b) {
|
||||
// v1 = a; v2 = b; v3 = v1 + v2; ret v3
|
||||
return me._header() + me._const(1, a) + me._const(2, b) + me._binop(3, "Add", 1, 2) + me._ret(3) + me._footer()
|
||||
}
|
||||
// Emit: if (a > b) return 1 else return 0 (multi-block)
|
||||
emit_if_gt_i64(a, b) {
|
||||
local h = "{\n \"kind\": \"MIR\",\n \"schema_version\": \"1.0\",\n \"functions\": [\n { \"name\": \"Main.main\", \"entry\": 1,\n \"blocks\": [\n"
|
||||
local b1 = " {\"id\":1, \"instructions\": [\n"
|
||||
+ me._const(1, a)
|
||||
+ me._const(2, b)
|
||||
+ " {\"op\":\"compare\",\"dst\":3,\"lhs\":1,\"rhs\":2,\"operation\":\"Gt\"},\n"
|
||||
+ " {\"op\":\"branch\",\"cond\":3,\"then\":2,\"else\":3}\n"
|
||||
+ " ] }\n"
|
||||
local b2 = " , {\"id\":2, \"instructions\": [\n"
|
||||
+ me._const(4, 1)
|
||||
+ me._ret(4)
|
||||
+ " ] }\n"
|
||||
local b3 = " , {\"id\":3, \"instructions\": [\n"
|
||||
+ me._const(5, 0)
|
||||
+ me._ret(5)
|
||||
+ " ] }\n"
|
||||
local f = " ]\n }\n ]\n}\n"
|
||||
return h + b1 + b2 + b3 + f
|
||||
}
|
||||
|
||||
// Emit: if (a == b) return 1 else return 0
|
||||
emit_if_eq_i64(a, b) {
|
||||
local h = "{\n \"kind\": \"MIR\",\n \"schema_version\": \"1.0\",\n \"functions\": [\n { \"name\": \"Main.main\", \"entry\": 1,\n \"blocks\": [\n"
|
||||
local b1 = " {\"id\":1, \"instructions\": [\n"
|
||||
+ me._const(1, a)
|
||||
+ me._const(2, b)
|
||||
+ " {\"op\":\"compare\",\"dst\":3,\"lhs\":1,\"rhs\":2,\"operation\":\"Eq\"},\n"
|
||||
+ " {\"op\":\"branch\",\"cond\":3,\"then\":2,\"else\":3}\n"
|
||||
+ " ] }\n"
|
||||
local b2 = " , {\"id\":2, \"instructions\": [\n"
|
||||
+ me._const(4, 1)
|
||||
+ me._ret(4)
|
||||
+ " ] }\n"
|
||||
local b3 = " , {\"id\":3, \"instructions\": [\n"
|
||||
+ me._const(5, 0)
|
||||
+ me._ret(5)
|
||||
+ " ] }\n"
|
||||
local f = " ]\n }\n ]\n}\n"
|
||||
return h + b1 + b2 + b3 + f
|
||||
}
|
||||
|
||||
// Emit: if (a != b) return 1 else return 0
|
||||
emit_if_ne_i64(a, b) {
|
||||
local h = "{\n \"kind\": \"MIR\",\n \"schema_version\": \"1.0\",\n \"functions\": [\n { \"name\": \"Main.main\", \"entry\": 1,\n \"blocks\": [\n"
|
||||
local b1 = " {\"id\":1, \"instructions\": [\n"
|
||||
+ me._const(1, a)
|
||||
+ me._const(2, b)
|
||||
+ " {\"op\":\"compare\",\"dst\":3,\"lhs\":1,\"rhs\":2,\"operation\":\"Ne\"},\n"
|
||||
+ " {\"op\":\"branch\",\"cond\":3,\"then\":2,\"else\":3}\n"
|
||||
+ " ] }\n"
|
||||
local b2 = " , {\"id\":2, \"instructions\": [\n"
|
||||
+ me._const(4, 1)
|
||||
+ me._ret(4)
|
||||
+ " ] }\n"
|
||||
local b3 = " , {\"id\":3, \"instructions\": [\n"
|
||||
+ me._const(5, 0)
|
||||
+ me._ret(5)
|
||||
+ " ] }\n"
|
||||
local f = " ]\n }\n ]\n}\n"
|
||||
return h + b1 + b2 + b3 + f
|
||||
}
|
||||
|
||||
// Emit diamond with PHI:
|
||||
// if (a > b) then v=tv else v=ev; return v
|
||||
emit_diamond_phi_gt_i64(a, b, tv, ev) {
|
||||
local h = "{\n \"kind\": \"MIR\",\n \"schema_version\": \"1.0\",\n \"functions\": [\n { \"name\": \"Main.main\", \"entry\": 1,\n \"blocks\": [\n"
|
||||
// bb1: compare + branch to bb2/bb3
|
||||
local b1 = " {\"id\":1, \"instructions\": [\n"
|
||||
+ me._const(1, a)
|
||||
+ me._const(2, b)
|
||||
+ " {\"op\":\"compare\",\"dst\":3,\"lhs\":1,\"rhs\":2,\"operation\":\"Gt\"},\n"
|
||||
+ " {\"op\":\"branch\",\"cond\":3,\"then\":2,\"else\":3}\n"
|
||||
+ " ] }\n"
|
||||
// bb2: then path → const tv; jump merge
|
||||
local b2 = " , {\"id\":2, \"instructions\": [\n"
|
||||
+ me._const(4, tv)
|
||||
+ " {\"op\":\"jump\",\"target\":4}\n"
|
||||
+ " ] }\n"
|
||||
// bb3: else path → const ev; jump merge
|
||||
local b3 = " , {\"id\":3, \"instructions\": [\n"
|
||||
+ me._const(5, ev)
|
||||
+ " {\"op\":\"jump\",\"target\":4}\n"
|
||||
+ " ] }\n"
|
||||
// bb4: phi merge; ret v6
|
||||
local b4 = " , {\"id\":4, \"instructions\": [\n"
|
||||
+ " {\"op\":\"phi\",\"dst\":6,\"values\":[{\"block\":2,\"value\":4},{\"block\":3,\"value\":5}]},\n"
|
||||
+ me._ret(6)
|
||||
+ " ] }\n"
|
||||
local f = " ]\n }\n ]\n}\n"
|
||||
return h + b1 + b2 + b3 + b4 + f
|
||||
}
|
||||
}
|
||||
|
||||
static box MinMirEmitterMain { main(args){ return 0 } }
|
||||
9
lang/src/opt/hako_module.toml
Normal file
9
lang/src/opt/hako_module.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[module]
|
||||
name = "selfhost.opt"
|
||||
version = "1.0.0"
|
||||
|
||||
[exports]
|
||||
# Point AotPrepBox to the actual implementation under llvm_ir/boxes
|
||||
AotPrepBox = "../llvm_ir/boxes/aot_prep.hako"
|
||||
VMHotPathBox = "../vm/opt/vm_hot_path.hako"
|
||||
README = "readme.md"
|
||||
169
lang/src/opt/mir_aot_prep.hako
Normal file
169
lang/src/opt/mir_aot_prep.hako
Normal file
@ -0,0 +1,169 @@
|
||||
// AotPrepBox — MIR(JSON) prepare stage (Phase 20.12)
|
||||
// Responsibility:
|
||||
// - JSON normalization (canonicalization hook)
|
||||
// - Safe, local constant fold for minimal single-block const/binop/ret (observability/stability first)
|
||||
// Non-responsibility:
|
||||
// - Global MIR rewrites, control-flow changes, or optimizer passes(将来の AotPrepV2 へ)
|
||||
|
||||
using "lang/src/shared/mir/mir_io_box.hako" as MirIoBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box AotPrepBox {
|
||||
// Entry: return prepped JSON string
|
||||
prep(json) {
|
||||
if json == null { return json }
|
||||
local norm = MirIoBox.normalize(json)
|
||||
// Drop trivially-unused string consts (safe, local)
|
||||
local d = me._drop_unused_string_consts(norm)
|
||||
if d != null && d != "" { norm = d }
|
||||
// Try a minimal fold; if not applicable, return normalized JSON
|
||||
local f = me._fold_single_block_binop(norm)
|
||||
if f != null && f != "" { return f }
|
||||
return norm
|
||||
}
|
||||
|
||||
// Minimal fold: pattern = [const i64 dst=1, const i64 dst=2, binop("+|-|*") dst=3 lhs=1 rhs=2, ret value=3]
|
||||
_fold_single_block_binop(json) {
|
||||
// Fast screen: must contain keys we expect
|
||||
if json.indexOf("\"instructions\"") < 0 { return "" }
|
||||
// Find instructions array and slice it (escape-aware)
|
||||
local pos = JsonCursorBox.find_key_dual(json, "\"instructions\"", "\"instructions\"", 0)
|
||||
if pos < 0 { return "" }
|
||||
local start = json.indexOf("[", pos)
|
||||
if start < 0 { return "" }
|
||||
local end = JsonCursorBox.seek_array_end(json, start)
|
||||
if end < 0 { return "" }
|
||||
local body = json.substring(start+1, end) // inside [ ... ]
|
||||
// Narrow textual pattern checks (builder/wrapper generated JSON)
|
||||
local p0 = body.indexOf("\"op\":\"const\"")
|
||||
if p0 < 0 { return "" }
|
||||
// Extract the two immediate values by scanning digits after known markers
|
||||
local k1 = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||
local a_pos = body.indexOf(k1, p0)
|
||||
if a_pos < 0 { return "" }
|
||||
a_pos = a_pos + k1.size()
|
||||
local a_s = JsonCursorBox.digits_from(body, a_pos)
|
||||
if a_s == null || a_s == "" { return "" }
|
||||
local p1 = body.indexOf("\"op\":\"const\"", a_pos)
|
||||
if p1 < 0 { return "" }
|
||||
local b_pos = body.indexOf(k1, p1)
|
||||
if b_pos < 0 { return "" }
|
||||
b_pos = b_pos + k1.size()
|
||||
local b_s = JsonCursorBox.digits_from(body, b_pos)
|
||||
if b_s == null || b_s == "" { return "" }
|
||||
// operation symbol
|
||||
local opk_pos = body.indexOf("\"operation\":\"", b_pos)
|
||||
if opk_pos < 0 { return "" }
|
||||
opk_pos = opk_pos + 14
|
||||
local opk = body.substring(opk_pos, opk_pos+1)
|
||||
if !(opk == "+" || opk == "-" || opk == "*") { return "" }
|
||||
// ret id must be 3 in wrapper style; safe check
|
||||
if body.indexOf("\"op\":\"ret\"", opk_pos) < 0 { return "" }
|
||||
// Compute
|
||||
local av = me._to_i64(a_s)
|
||||
local bv = me._to_i64(b_s)
|
||||
local rv = 0
|
||||
if opk == "+" { rv = av + bv }
|
||||
else if opk == "-" { rv = av - bv }
|
||||
else { rv = av * bv }
|
||||
// Build folded instruction array: [const rv -> dst:1, ret 1]
|
||||
local folded = "[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + (""+rv) + "}},{\"op\":\"ret\",\"value\":1}]"
|
||||
// Splice back into whole JSON and return
|
||||
return json.substring(0, start+1) + folded + json.substring(end, json.size())
|
||||
}
|
||||
|
||||
_to_i64(s) {
|
||||
// crude but sufficient for our immediate range
|
||||
local i = 0; local neg = 0
|
||||
if s.size() > 0 && s.substring(0,1) == "-" { neg = 1; i = 1 }
|
||||
local out = 0
|
||||
loop (i < s.size()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch < "0" || ch > "9" { break }
|
||||
out = out * 10 + (ch - "0")
|
||||
i = i + 1
|
||||
}
|
||||
if neg { out = 0 - out }
|
||||
return out
|
||||
}
|
||||
|
||||
// Remove const string-handle instructions whose dst is never referenced.
|
||||
// Heuristic (safe): textual scan limited to const(StringBox) objects; validate no reference patterns after.
|
||||
_drop_unused_string_consts(json) {
|
||||
if json == null { return "" }
|
||||
local i = 0
|
||||
local changed = 0
|
||||
// Pattern head we search: "op":"const","dst":<N>,"value":{"type":{"box_type":"StringBox"
|
||||
local pat = "\"op\":\"const\",\"dst\":"
|
||||
loop (true) {
|
||||
local p = json.indexOf(pat, i)
|
||||
if p < 0 { break }
|
||||
// Parse dst number
|
||||
local pnum = p + pat.size()
|
||||
local digits = JsonCursorBox.digits_from(json, pnum)
|
||||
if digits == null || digits == "" { i = p + 1; continue }
|
||||
local dst_s = digits
|
||||
// Check it is a StringBox const
|
||||
local val_pos = json.indexOf("\"value\":", pnum)
|
||||
if val_pos < 0 { i = p + 1; continue }
|
||||
local box_pos = json.indexOf("\"box_type\":\"StringBox\"", val_pos)
|
||||
if box_pos < 0 { i = p + 1; continue }
|
||||
// Determine end of this const object robustly by brace depth scan
|
||||
local obj_start = json.lastIndexOf("{", p)
|
||||
if obj_start < 0 { obj_start = p }
|
||||
local obj_end = me._seek_object_end(json, obj_start)
|
||||
if obj_end < 0 { i = p + 1; continue }
|
||||
// Validate dst is unused after this object
|
||||
local tail = json.substring(obj_end+1, json.size())
|
||||
// Search common reference patterns: ":<dst>" after a key
|
||||
local ref = ":" + dst_s
|
||||
if tail.indexOf(ref) >= 0 {
|
||||
i = p + 1; continue
|
||||
}
|
||||
// Remove this object (and a trailing comma if present)
|
||||
local cut_left = obj_start
|
||||
local cut_right = obj_end + 1
|
||||
// Trim a single trailing comma to keep JSON valid in arrays
|
||||
if cut_right < json.size() {
|
||||
local ch = json.substring(cut_right, cut_right+1)
|
||||
if ch == "," { cut_right = cut_right + 1 }
|
||||
}
|
||||
json = json.substring(0, cut_left) + json.substring(cut_right, json.size())
|
||||
changed = 1
|
||||
i = cut_left
|
||||
}
|
||||
if changed == 1 { return json }
|
||||
return ""
|
||||
}
|
||||
|
||||
// Seek the matching '}' for the object that starts at `start` (points to '{').
|
||||
// Handles nested objects and string literals with escapes.
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.size() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.size()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return i }
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
static box AotPrepBoxMain { main(args){ return 0 } }
|
||||
23
lang/src/opt/mir_inline_expand.hako
Normal file
23
lang/src/opt/mir_inline_expand.hako
Normal file
@ -0,0 +1,23 @@
|
||||
// MirInlineExpand — MIR prepass inliner (Phase 20.12 scaffold)
|
||||
// Responsibility:
|
||||
// - Plan-only scaffold for a small-function, pure, non-recursive inline expansion
|
||||
// - Gated by env NYASH_MIR_INLINE_EXPAND=1 via runner (src/runner/modes/common_util/exec.rs)
|
||||
// Non-Responsibility:
|
||||
// - Actual inlining logic (to be implemented incrementally)
|
||||
|
||||
using "lang/src/shared/mir/mir_io_box.hako" as MirIoBox
|
||||
|
||||
static box MirInlineExpand {
|
||||
// Entry: return (possibly) transformed JSON path; v0 returns input as-is.
|
||||
prep(json_path) {
|
||||
// v0: identity (no-op). This stub allows gating and pipeline wiring
|
||||
// without changing semantics. Subsequent phases will:
|
||||
// - Parse JSON
|
||||
// - Detect inlineable small pure functions (no recursion, small blocks)
|
||||
// - Substitute call sites with function bodies at JSON-level
|
||||
return json_path
|
||||
}
|
||||
}
|
||||
|
||||
static box MirInlineExpandMain { main(args){ return 0 } }
|
||||
|
||||
17
lang/src/opt/readme.md
Normal file
17
lang/src/opt/readme.md
Normal file
@ -0,0 +1,17 @@
|
||||
# selfhost/opt — Optimization/Preparation Boxes
|
||||
|
||||
Purpose
|
||||
- Provide small, opt-in preparation/optimization stages implemented in Hakorune.
|
||||
- First step: AotPrepBox — MIR(JSON) normalize + safe local const-fold for single-block const/binop/ret.
|
||||
|
||||
Responsibilities
|
||||
- JSON normalization via shared MirIoBox (canonicalization hook).
|
||||
- Behavior-preserving, local transforms only; Fail‑Fast for unsupported shapes.
|
||||
|
||||
Non-Responsibilities
|
||||
- Global CFG rewrites, SSA rebuild, or MIR semantics changes.
|
||||
|
||||
Gates
|
||||
- Runner uses HAKO_AOT_PREP=1 to save “prepared” MIR sidecar after emit‑exe.
|
||||
- Later we may switch compile input behind a dedicated flag once stable.
|
||||
|
||||
28
lang/src/runner/README.md
Normal file
28
lang/src/runner/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Runner Facade (Script‑First) — Phase 20.10
|
||||
|
||||
Responsibility
|
||||
- Provide a thin, opt‑in runner facade in Hakorune (script) to orchestrate entry selection and pre/post hooks.
|
||||
- Delegates actual execution to existing backends (Rust VM / LLVM). No behavior change by default.
|
||||
|
||||
Gate
|
||||
- Enable with `HAKO_SCRIPT_RUNNER=1` (default OFF). In 20.10 this runner is not wired by default; wiring will be added behind the gate.
|
||||
|
||||
Contracts (draft)
|
||||
- Entry: `Runner.run(entry: string, args: array<string>) -> i64`
|
||||
- Pre‑hooks: validate entry, shape args, emit diagnostics (short tokens) on failure.
|
||||
- Post‑hooks: normalize result (e.g., quiet result mode), optional metrics/logging.
|
||||
|
||||
Notes
|
||||
- Keep this layer pure and free of platform I/O. Defer I/O to C‑ABI utilities (`hako_*`).
|
||||
- Fail‑Fast: invalid entry/args → emit short diagnostics and return non‑zero.
|
||||
- Box‑First: add adapters for boundary objects (args, env) instead of sprinkling conditions.
|
||||
|
||||
Short Diagnostics & Observability
|
||||
- Pre‑invoke emits stable one‑liners for smokes:
|
||||
- Success: `[script-runner] invoke` (stdout)
|
||||
- Failure (dev injection or panic): `[script-runner] invoke: FAIL` (stdout)
|
||||
- The runner wiring prints a gate trace to stderr: `[script-runner] gate=on (facade)`.
|
||||
|
||||
Dev‑only toggle (TTL: Phase 20.10 bring‑up)
|
||||
- `HAKO_SCRIPT_RUNNER_FORCE_FAIL=1` forces the pre‑invoke to emit `[script-runner] invoke: FAIL` without executing the facade program.
|
||||
- Scope: tests/smokes only. Remove after runner wiring stabilizes (documented here as a temporary aid).
|
||||
13
lang/src/runner/core/README.md
Normal file
13
lang/src/runner/core/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Runner Core (Plan Builder)
|
||||
|
||||
Scope
|
||||
- Decision-only layer to build a `RunnerPlan` from CLI/ENV inputs.
|
||||
- No I/O or process execution here. Rust (Floor) applies the plan.
|
||||
|
||||
Status
|
||||
- Phase 20.26‑C scaffold. Opt‑in via `HAKO_RUNNER_PLAN=1` once wired.
|
||||
|
||||
Contract (MVP)
|
||||
- Provide `RunnerPlanBuilder.build(args)` that returns a small JSON object with a minimal set of fields:
|
||||
- `{"action":"ExecuteCore|ExecuteVM|ExecuteNyLlvmc|Skip|Error", "gate_c":bool, "engine":"core|vm|llvm", "plugins":bool, "quiet":bool}`
|
||||
- Fail‑Fast on ambiguity; no silent fallbacks.
|
||||
34
lang/src/runner/core/plan_builder.hako
Normal file
34
lang/src/runner/core/plan_builder.hako
Normal file
@ -0,0 +1,34 @@
|
||||
// RunnerPlanBuilder (JSON Plan) — Phase 20.26‑C
|
||||
// Responsibility: Build a minimal RunnerPlan JSON from a tiny JSON payload.
|
||||
// Contract: build/1 accepts a JSON-like string and returns a JSON object string:
|
||||
// {"action":"ExecuteCore|ExecuteVM|ExecuteNyLlvmc|Skip|Error","gate_c":bool,
|
||||
// "engine":"core|vm|llvm", "plugins":bool, "quiet":bool }
|
||||
|
||||
static box RunnerPlanBuilder {
|
||||
build(j) {
|
||||
// MVP: naive scan (no JSON parser dependency).
|
||||
// Priority: explicit backend → ExecuteVM/ExecuteNyLlvmc;
|
||||
// boxes directive → ExecuteBoxes;
|
||||
// else if gate_c → ExecuteCore; else Skip.
|
||||
local gate = j.indexOf("\"gate_c\":true") >= 0
|
||||
local act = "Skip"
|
||||
if (j.indexOf("\"backend\":\"vm\"") >= 0) { act = "ExecuteVM" }
|
||||
else if (j.indexOf("\"backend\":\"llvm\"") >= 0) { act = "ExecuteNyLlvmc" }
|
||||
// boxes toggle (optional). If present and no stronger action decided, produce ExecuteBoxes
|
||||
local boxes_val = ""
|
||||
if (j.indexOf("\"boxes\":\"hako\"") >= 0) { boxes_val = "hako" }
|
||||
else if (j.indexOf("\"boxes\":\"rust\"") >= 0) { boxes_val = "rust" }
|
||||
if (act == "Skip" && boxes_val != "") { act = "ExecuteBoxes" }
|
||||
else if (act == "Skip" && gate) { act = "ExecuteCore" }
|
||||
|
||||
// Compose a compact JSON string (keys kept minimal and stable)
|
||||
local plan = "{\"action\":\"" + act + "\"," +
|
||||
"\"gate_c\":" + (gate ? "true" : "false") + "," +
|
||||
"\"engine\":\"core\"," +
|
||||
"\"plugins\":false," +
|
||||
"\"quiet\":true" +
|
||||
(boxes_val != "" ? ",\"boxes\":\"" + boxes_val + "\"" : "") +
|
||||
"}"
|
||||
return plan
|
||||
}
|
||||
}
|
||||
15
lang/src/runner/gate_c/controller.hako
Normal file
15
lang/src/runner/gate_c/controller.hako
Normal file
@ -0,0 +1,15 @@
|
||||
// Gate‑C Controller (Phase 20.26)
|
||||
// Responsibility: Provide a thin, stable entry to route MIR(JSON v0)
|
||||
// through the Ny/Core dispatcher when a wrapper route is needed.
|
||||
|
||||
include "lang/src/vm/core/dispatcher.hako"
|
||||
|
||||
static box GateCController {
|
||||
// route_json/1: String(JSON v0) -> String(last line)
|
||||
// Contract: returns a printable string (numeric or tag). No side effects.
|
||||
route_json(j) {
|
||||
// Delegate to the Core dispatcher runner
|
||||
return call("NyVmDispatcher.run/1", j)
|
||||
}
|
||||
}
|
||||
|
||||
7
lang/src/runner/hako_module.toml
Normal file
7
lang/src/runner/hako_module.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[module]
|
||||
name = "selfhost.runner"
|
||||
version = "1.0.0"
|
||||
|
||||
[exports]
|
||||
Runner = "runner_facade.hako"
|
||||
|
||||
9
lang/src/runner/launcher.hako
Normal file
9
lang/src/runner/launcher.hako
Normal file
@ -0,0 +1,9 @@
|
||||
// launcher.hako — Minimal selfhost launcher (AOT target)
|
||||
// Goal: produce lang/bin/hakorune via tools/build_llvm.sh
|
||||
|
||||
static box Main {
|
||||
// No-args main() to avoid runtime Array allocation in AOT
|
||||
main(){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
23
lang/src/runner/runner_facade.hako
Normal file
23
lang/src/runner/runner_facade.hako
Normal file
@ -0,0 +1,23 @@
|
||||
// runner_facade.hako — Phase 20.10 Script Runner (facade, draft)
|
||||
// Gate: HAKO_SCRIPT_RUNNER=1 (not wired by default in 20.10)
|
||||
|
||||
static box Runner {
|
||||
// Run facade. Returns process exit code (i64).
|
||||
run(entry, args){
|
||||
// Validate entry (simple dotted form expected)
|
||||
if (entry == null || entry == "") {
|
||||
call("env.console.warn/1", "VALIDATION");
|
||||
return 2;
|
||||
}
|
||||
// Trace invocation (stable short line for smokes) — opt-in
|
||||
// HAKO_SCRIPT_RUNNER_TRACE=1 で出力(既定は静穏)
|
||||
local tr = call("env.local.get/1", "HAKO_SCRIPT_RUNNER_TRACE");
|
||||
if tr && tr != "0" && tr != "false" {
|
||||
print("[script-runner] invoke");
|
||||
}
|
||||
// Future: pre-hooks (env snapshot / quiet-result policy)
|
||||
// Delegate to backend via existing CLI (placeholder; not wired in 20.10)
|
||||
// This facade is documented first; wiring is added from Rust side behind a gate.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
69
lang/src/runtime/gc/gc_box.hako
Normal file
69
lang/src/runtime/gc/gc_box.hako
Normal file
@ -0,0 +1,69 @@
|
||||
// GcBox — Policy wrapper (Hakorune) around host GC externs
|
||||
// Phase 20.8: docs/導線優先(既定OFF)。Rustはデータ平面(走査/バリア/セーフポイント)、
|
||||
// Hakorune はポリシー平面(しきい値/スケジューリング/ログ)を担当するよ。
|
||||
// 参考: docs/development/architecture/gc/policy-vs-data-plane.md
|
||||
|
||||
static box GcBox {
|
||||
// Return JSON string with counters {safepoints, barrier_reads, barrier_writes}
|
||||
stats() {
|
||||
return call("env.gc.stats/0")
|
||||
}
|
||||
|
||||
// Return total roots count (host handles + modules), best‑effort integer
|
||||
roots_snapshot() {
|
||||
return call("env.gc.roots_snapshot/0")
|
||||
}
|
||||
|
||||
// Request collection (no‑op until host supports it)
|
||||
collect() {
|
||||
// Host may ignore; keep Fail‑Fast if extern missing
|
||||
call("env.gc.collect/0")
|
||||
}
|
||||
|
||||
// Optional lifecycle hooks (no‑op unless host implements)
|
||||
start() { call("env.gc.start/0"); }
|
||||
stop() { call("env.gc.stop/0"); }
|
||||
|
||||
// Example (dev): a tiny cadence policy, OFF by default.
|
||||
// Enable with: HAKO_GC_POLICY_TICK=1 (docs only; call manually from scripts)
|
||||
policy_tick() {
|
||||
if (call("env.local.get/1", "HAKO_GC_POLICY_TICK") == "1") {
|
||||
// Read metrics and print a compact line for observation
|
||||
local s = me.stats()
|
||||
call("env.console.log/1", "[GcBox] stats=" + s)
|
||||
// Example threshold (no-op unless host implements collect):
|
||||
// me.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Example (dev): run simple cadence based on env thresholds (strings; best-effort)
|
||||
// HAKO_GC_POLICY_LOG=1 → print stats each tick
|
||||
// HAKO_GC_POLICY_FORCE=1 → call collect() each tick (may be no-op; guard expected)
|
||||
// HAKO_GC_POLICY_EVERY_N=K → best-effort modulus trigger (tick_count % K == 0)
|
||||
tick_with_policy(tick_count) {
|
||||
local log = call("env.local.get/1", "HAKO_GC_POLICY_LOG")
|
||||
local every = call("env.local.get/1", "HAKO_GC_POLICY_EVERY_N")
|
||||
local force = call("env.local.get/1", "HAKO_GC_POLICY_FORCE")
|
||||
|
||||
if (log == "1") {
|
||||
call("env.console.log/1", "[GcBox] stats=" + me.stats())
|
||||
}
|
||||
if (force == "1") {
|
||||
// Gate: host may not implement collect() yet
|
||||
collect()
|
||||
return 0
|
||||
}
|
||||
if (every != "") {
|
||||
// crude parse: treat non-empty string as integer if equals to tick_count string
|
||||
// consumers should pass small positive K
|
||||
local k = every
|
||||
// Only attempt modulus when both are simple digits
|
||||
// (language JSON helpers or numeric ops may be used when available)
|
||||
if (k == "1") {
|
||||
collect()
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
62
lang/src/runtime/memory/arc_box.hako
Normal file
62
lang/src/runtime/memory/arc_box.hako
Normal file
@ -0,0 +1,62 @@
|
||||
// arc_box.hako — ArcBox (policy plane)
|
||||
// Responsibility: simple reference counting semantics in Hakorune space.
|
||||
// Storage: uses env.local.get/set with key prefix "arc:" to keep per-pointer counts.
|
||||
// Data plane: when available, host-side env.arc.* can replace these, but this MVP
|
||||
// remains pure to avoid native coupling. Optional free-on-zero via env.mem.free/1
|
||||
// guarded by HAKO_ARC_FREE_ON_ZERO=1 (NYASH_ alias honored via runner env mirroring).
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box ArcBox {
|
||||
_key(ptr) { return "ARC_" + ("" + ptr) }
|
||||
|
||||
_debug_on() {
|
||||
local d = call("env.local.get/1", "HAKO_DEBUG_ARC")
|
||||
if d == null || d == "" { d = call("env.local.get/1", "NYASH_DEBUG_ARC") }
|
||||
return d == "1"
|
||||
}
|
||||
|
||||
_log(msg) { if me._debug_on() { call("env.console.log/1", "[ArcBox] " + msg) } }
|
||||
|
||||
// Read current count (>=0) or -1 if unset/unknown)
|
||||
_get_count(ptr) { return call("env.arc.count/1", ptr) }
|
||||
|
||||
// Create a new ARC handle with count=1. Fail if already exists.
|
||||
arc_birth(ptr) {
|
||||
local c = me._get_count(ptr)
|
||||
if c >= 0 { call("env.console.error/1", "[arc/already_exists]") return -1 }
|
||||
call("env.arc.birth/1", ptr)
|
||||
me._log("birth ptr=" + ("" + ptr) + " -> 1")
|
||||
return 1
|
||||
}
|
||||
|
||||
// Increment; Fail if unknown.
|
||||
arc_retain(ptr) {
|
||||
local c = call("env.arc.retain/1", ptr)
|
||||
if c < 0 { call("env.console.error/1", "[arc/unknown]") return -1 }
|
||||
me._log("retain ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(c))
|
||||
return c
|
||||
}
|
||||
|
||||
// Decrement; Fail on unknown or underflow. When reaches 0, optionally free via env.mem.free/1
|
||||
arc_release(ptr) {
|
||||
local n = call("env.arc.release/1", ptr)
|
||||
if n < 0 { return -1 }
|
||||
me._log("release ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(n))
|
||||
if n == 0 {
|
||||
local fz = call("env.local.get/1", "HAKO_ARC_FREE_ON_ZERO")
|
||||
if fz == null || fz == "" { fz = call("env.local.get/1", "NYASH_ARC_FREE_ON_ZERO") }
|
||||
if fz == "1" {
|
||||
// Best-effort free; ignore missing handler
|
||||
call("env.mem.free/1", ptr)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Return current count or -1 if unknown
|
||||
arc_count(ptr) { return me._get_count(ptr) }
|
||||
|
||||
// Sugar: clone = retain + return ptr (for chaining)
|
||||
arc_clone(ptr) { local r = me.arc_retain(ptr) if r < 0 { return -1 } return ptr }
|
||||
}
|
||||
67
lang/src/runtime/memory/refcell_box.hako
Normal file
67
lang/src/runtime/memory/refcell_box.hako
Normal file
@ -0,0 +1,67 @@
|
||||
// refcell_box.hako — RefCellBox (policy plane)
|
||||
// Responsibility: simple borrow/borrow_mut semantics with conflict checks.
|
||||
// Storage: uses env.local.get/set with key prefix "ref:" per-pointer state.
|
||||
// State encoding: "0" = idle, ">0" = shared borrows count, "-1" = mutable borrow active.
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box RefCellBox {
|
||||
_key(ptr) { return "ref:" + ("" + ptr) }
|
||||
|
||||
_debug_on() {
|
||||
local d = call("env.local.get/1", "HAKO_DEBUG_REFCELL")
|
||||
if d == null || d == "" { d = call("env.local.get/1", "NYASH_DEBUG_REFCELL") }
|
||||
return d == "1"
|
||||
}
|
||||
_log(msg) { if me._debug_on() { call("env.console.log/1", "[RefCellBox] " + msg) } }
|
||||
|
||||
_get(ptr) {
|
||||
local s = call("env.local.get/1", me._key(ptr))
|
||||
if s == null || s == "" { return 0 }
|
||||
return StringHelpers.to_i64(s)
|
||||
}
|
||||
_set(ptr, n) { call("env.local.set/2", me._key(ptr), StringHelpers.int_to_str(n)) }
|
||||
|
||||
// Initialize state to 0 (idempotent)
|
||||
init(ptr) { me._set(ptr, 0) return 0 }
|
||||
|
||||
// Shared borrow: allow when state >= 0; increments shared counter.
|
||||
try_borrow(ptr) {
|
||||
local st = me._get(ptr)
|
||||
if st < 0 { call("env.console.error/1", "[refcell/conflict_shared]") return -1 }
|
||||
me._set(ptr, st + 1)
|
||||
me._log("borrow ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(st + 1))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Mutable borrow: allow only when state == 0.
|
||||
try_borrow_mut(ptr) {
|
||||
local st = me._get(ptr)
|
||||
if st != 0 { call("env.console.error/1", "[refcell/conflict_mut]") return -1 }
|
||||
me._set(ptr, -1)
|
||||
me._log("borrow_mut ptr=" + ("" + ptr) + " -> -1")
|
||||
return 1
|
||||
}
|
||||
|
||||
// Release one shared borrow; Fail if not in shared state
|
||||
release_shared(ptr) {
|
||||
local st = me._get(ptr)
|
||||
if st <= 0 { call("env.console.error/1", "[refcell/release_shared_invalid]") return -1 }
|
||||
me._set(ptr, st - 1)
|
||||
me._log("release_shared ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(st - 1))
|
||||
return st - 1
|
||||
}
|
||||
|
||||
// Release mutable borrow; Fail if not in mut state
|
||||
release_mut(ptr) {
|
||||
local st = me._get(ptr)
|
||||
if st != -1 { call("env.console.error/1", "[refcell/release_mut_invalid]") return -1 }
|
||||
me._set(ptr, 0)
|
||||
me._log("release_mut ptr=" + ("" + ptr) + " -> 0")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Observe current state (debug aid): returns -1 (mut), 0 (idle), N>0 (shared count)
|
||||
state(ptr) { return me._get(ptr) }
|
||||
}
|
||||
|
||||
8
lang/src/runtime/meta/hako_module.toml
Normal file
8
lang/src/runtime/meta/hako_module.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[module]
|
||||
name = "selfhost.meta"
|
||||
version = "1.0.0"
|
||||
|
||||
[exports]
|
||||
UsingResolver = "using_resolver.hako"
|
||||
UsingDecision = "using_decision.hako"
|
||||
JsonShapeToMap = "json_shape_parser.hako"
|
||||
153
lang/src/runtime/meta/json_shape_parser.hako
Normal file
153
lang/src/runtime/meta/json_shape_parser.hako
Normal file
@ -0,0 +1,153 @@
|
||||
// JsonShapeToMap — minimal, opt‑in JSON→Map builder for Using shape (skeleton)
|
||||
// Note: This is a very small adapter intended for controlled inputs produced by
|
||||
// UsingResolver.shape/1. It does not implement a general JSON parser.
|
||||
|
||||
using "lang/src/shared/json/json_utils.hako" as JsonUtilsBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box JsonShapeToMap {
|
||||
_empty(){ return map({ using_paths: new ArrayBox(), modules: new ArrayBox(), aliases: map({}), packages: map({}) }) }
|
||||
|
||||
_parse_array_of_strings(arr_json){
|
||||
local out = new ArrayBox()
|
||||
if !arr_json || arr_json.size() < 2 { return out }
|
||||
local parts = JsonUtilsBox.split_top_level(arr_json)
|
||||
local i = 0
|
||||
loop(i < parts.size()){
|
||||
local v = StringHelpers.trim(parts.get(i))
|
||||
if v.size() >= 2 && v.substring(0,1) == "\"" && v.substring(v.size()-1, v.size()) == "\"" {
|
||||
out.push(JsonUtilsBox.unescape_string(v.substring(1, v.size()-1)))
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_split_object_pairs(obj_json){
|
||||
// Like split_top_level for arrays, but for object key:value pairs inside {...}
|
||||
local out = new ArrayBox()
|
||||
if !obj_json || obj_json.size() < 2 { return out }
|
||||
local n = obj_json.size()
|
||||
local i = 1
|
||||
local start = 1
|
||||
local depth = 0
|
||||
local in_string = 0
|
||||
loop(i < n - 1){
|
||||
local ch = obj_json.substring(i, i + 1)
|
||||
if in_string == 1 {
|
||||
if ch == "\\" { i = i + 2 } else { if ch == "\"" { in_string = 0 } i = i + 1 }
|
||||
} else {
|
||||
if ch == "\"" { in_string = 1 i = i + 1 }
|
||||
else {
|
||||
if ch == "{" || ch == "[" { depth = depth + 1 }
|
||||
else if ch == "}" || ch == "]" { depth = depth - 1 }
|
||||
else if ch == "," && depth == 0 {
|
||||
out.push(obj_json.substring(start, i))
|
||||
start = i + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if start < n - 1 { out.push(obj_json.substring(start, n - 1)) }
|
||||
return out
|
||||
}
|
||||
|
||||
_read_key_from_pair(pair_json){
|
||||
// pair_json: '"key" : value'
|
||||
local s = StringHelpers.trim(pair_json)
|
||||
if s.size() < 3 || s.substring(0,1) != "\"" { return null }
|
||||
local end = JsonUtilsBox.skip_string(s, 0)
|
||||
local raw = s.substring(0, end)
|
||||
// raw like "\"key\"@pos" from read_string; we used skip_string, so raw lacks marker
|
||||
// Remove quotes
|
||||
local key = JsonUtilsBox.unescape_string(raw.substring(1, raw.size()-1))
|
||||
return key
|
||||
}
|
||||
|
||||
_read_value_from_pair(pair_json){
|
||||
// Return raw JSON value string
|
||||
local s = StringHelpers.trim(pair_json)
|
||||
// find ':'
|
||||
local idx = StringHelpers.index_of(s, 0, ":")
|
||||
if idx < 0 { return null }
|
||||
idx = StringHelpers.skip_ws(s, idx + 1)
|
||||
local v = JsonUtilsBox.read_value(s, idx)
|
||||
// v includes '@pos' marker — strip it
|
||||
local at = StringHelpers.last_index_of(v, "@")
|
||||
if at >= 0 { return v.substring(0, at) } else { return v }
|
||||
}
|
||||
|
||||
_parse_modules(mods_json){
|
||||
local out = new ArrayBox()
|
||||
if !mods_json || mods_json.size() < 2 { return out }
|
||||
local parts = JsonUtilsBox.split_top_level(mods_json)
|
||||
local i = 0
|
||||
loop(i < parts.size()){
|
||||
local obj = StringHelpers.trim(parts.get(i))
|
||||
if obj.size() >= 2 && obj.substring(0,1) == "{" {
|
||||
local ns = JsonUtilsBox.extract_string_value(obj, "ns", "")
|
||||
local path = JsonUtilsBox.extract_string_value(obj, "path", "")
|
||||
out.push(map({ ns: ns, path: path }))
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_parse_aliases(obj_json){
|
||||
local out = map({})
|
||||
if !obj_json || obj_json.size() < 2 { return out }
|
||||
local pairs = me._split_object_pairs(obj_json)
|
||||
local i = 0
|
||||
loop(i < pairs.size()){
|
||||
local p = pairs.get(i)
|
||||
local key = me._read_key_from_pair(p)
|
||||
local val_raw = me._read_value_from_pair(p)
|
||||
if key != null && val_raw != null {
|
||||
local v = StringHelpers.trim(val_raw)
|
||||
if v.size() >= 2 && v.substring(0,1) == "\"" && v.substring(v.size()-1, v.size()) == "\"" {
|
||||
out.set(key, JsonUtilsBox.unescape_string(v.substring(1, v.size()-1)))
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_parse_packages(obj_json){
|
||||
local out = map({})
|
||||
if !obj_json || obj_json.size() < 2 { return out }
|
||||
local pairs = me._split_object_pairs(obj_json)
|
||||
local i = 0
|
||||
loop(i < pairs.size()){
|
||||
local p = pairs.get(i)
|
||||
local key = me._read_key_from_pair(p)
|
||||
local val_raw = me._read_value_from_pair(p)
|
||||
if key != null && val_raw != null {
|
||||
local kind = JsonUtilsBox.extract_string_value(val_raw, "kind", "")
|
||||
local path = JsonUtilsBox.extract_string_value(val_raw, "path", "")
|
||||
local main = JsonUtilsBox.extract_string_value(val_raw, "main", "")
|
||||
out.set(key, map({ kind: kind, path: path, main: main }))
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// parse(json) -> Map with keys: using_paths(Array), modules(Array of Map), aliases(Map), packages(Map)
|
||||
parse(json){
|
||||
if !json { return me._empty() }
|
||||
// Extract each segment
|
||||
local using_arr = JsonUtilsBox.extract_value(json, "using_paths")
|
||||
local mods_arr = JsonUtilsBox.extract_value(json, "modules")
|
||||
local aliases_obj = JsonUtilsBox.extract_value(json, "aliases")
|
||||
local packages_obj = JsonUtilsBox.extract_value(json, "packages")
|
||||
|
||||
local using_paths = me._parse_array_of_strings(using_arr)
|
||||
local modules = me._parse_modules(mods_arr)
|
||||
local aliases = me._parse_aliases(aliases_obj)
|
||||
local packages = me._parse_packages(packages_obj)
|
||||
return map({ using_paths: using_paths, modules: modules, aliases: aliases, packages: packages })
|
||||
}
|
||||
}
|
||||
13
lang/src/runtime/meta/using_decision.hako
Normal file
13
lang/src/runtime/meta/using_decision.hako
Normal file
@ -0,0 +1,13 @@
|
||||
// UsingDecision — convenience meta box to decide OK/FAIL based on UsingResolver shape
|
||||
using "selfhost.meta.UsingResolver"
|
||||
|
||||
static box UsingDecision {
|
||||
decide(token){
|
||||
local r = UsingResolver.resolve(token)
|
||||
if !r { return "FAIL" }
|
||||
local a = r.get("using_paths"); local m = r.get("modules"); local al = r.get("aliases"); local pk = r.get("packages"); local po = r.get("policy")
|
||||
if !a || !m || !al || !pk || !po { return "FAIL" }
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
31
lang/src/runtime/meta/using_resolver.hako
Normal file
31
lang/src/runtime/meta/using_resolver.hako
Normal file
@ -0,0 +1,31 @@
|
||||
// using_resolver.hako — Meta Using Resolver (lang-side, minimal)
|
||||
// Public name (SSOT): UsingResolver.resolve/1
|
||||
|
||||
static box UsingResolver {
|
||||
// Minimal shape (Phase‑20.10): return an empty resolver result.
|
||||
// Shape: { using_paths: [], modules: [], aliases: {}, packages: {}, policy: {} }
|
||||
// Behavior-invariant: no I/O, no host-slot dependence.
|
||||
resolve(_token){
|
||||
return map({
|
||||
using_paths: new ArrayBox(),
|
||||
modules: new ArrayBox(),
|
||||
aliases: map({}),
|
||||
packages: map({}),
|
||||
policy: map({})
|
||||
});
|
||||
}
|
||||
|
||||
// stats/1 — Return minimal JSON counts (lang-side observability; behavior-invariant)
|
||||
// Shape: {"modules":N,"paths":P,"aliases":A}
|
||||
stats(_token){
|
||||
// Minimal, side-effect free, no Map/Array construction
|
||||
return "{\"modules\":0,\"paths\":0,\"aliases\":0}";
|
||||
}
|
||||
|
||||
// shape/1 — Return minimal resolver shape as JSON (behavior-invariant)
|
||||
// Shape JSON (keys only; values are empty):
|
||||
// {"using_paths":[], "modules":[], "aliases":{}, "packages":{}}
|
||||
shape(_token){
|
||||
return "{\"using_paths\":[],\"modules\":[],\"aliases\":{},\"packages\":{}}";
|
||||
}
|
||||
}
|
||||
11
lang/src/selfhost/hako_module.toml
Normal file
11
lang/src/selfhost/hako_module.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[module]
|
||||
name = "selfhost.mir_builder"
|
||||
version = "0.1.0"
|
||||
|
||||
[exports]
|
||||
README = "mir_builder/README.md"
|
||||
LayerGuard = "mir_builder/LAYER_GUARD.hako"
|
||||
Builder = "mir_builder/builder.hako"
|
||||
Phi = "mir_builder/phi.hako"
|
||||
Verify = "mir_builder/verify.hako"
|
||||
|
||||
9
lang/src/selfhost/mir_builder/LAYER_GUARD.hako
Normal file
9
lang/src/selfhost/mir_builder/LAYER_GUARD.hako
Normal file
@ -0,0 +1,9 @@
|
||||
// LAYER_GUARD — selfhost.mir_builder
|
||||
// この層の責務: MIR(JSON v0) の生成(将来的に)。VM/LLVM/実行は非対象。
|
||||
|
||||
static box LayerGuard {
|
||||
name() { return "selfhost.mir_builder" }
|
||||
allowed_imports() { return ["json", "mir_spec", "fs"] }
|
||||
forbidden_imports() { return ["vm", "llvm", "runtime", "plugins"] }
|
||||
}
|
||||
|
||||
30
lang/src/selfhost/mir_builder/README.md
Normal file
30
lang/src/selfhost/mir_builder/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
Self‑Host MIR Builder (Scaffold) — Phase‑20.12b
|
||||
|
||||
Purpose
|
||||
- Prepare a minimal, opt‑in skeleton to generate MIR(JSON v0) in Hakorune (self‑host) alongside the current Rust generator.
|
||||
- Keep default behavior unchanged (Rust MIR remains the source of truth). This module is emit‑only in early phases.
|
||||
|
||||
Scope (this scaffold)
|
||||
- Files: builder.hako, phi.hako, verify.hako, LAYER_GUARD.hako
|
||||
- Behavior: no‑op/identity placeholders with clearly defined interfaces and gates.
|
||||
- Docs: specs/mir-json-v0.md describes the minimal JSON v0 shape targeted by this builder.
|
||||
|
||||
Non‑Goals (now)
|
||||
- Replacing Rust MIR generation by default
|
||||
- Emitting full MIR coverage (call/extern/boxcall/complex boxes)
|
||||
|
||||
Interfaces (subject to evolution)
|
||||
- SelfhostMirBuilder.build(ast_or_src_path) -> json_path (emit‑only; v0 returns input path)
|
||||
- SelfhostMirVerify.verify(json_path) -> bool/int (0=ok; v0 always ok)
|
||||
- SelfhostPhiBox helpers (shape only; no logic yet)
|
||||
|
||||
Gates (opt‑in)
|
||||
- NYASH_USE_NY_COMPILER=1 → future: emit‑only builder path
|
||||
- NYASH_JSON_ONLY=1 → future: sidecar JSON dump for parity check
|
||||
|
||||
Layer Guard
|
||||
- See LAYER_GUARD.hako — allowed imports are restricted to shared JSON/MIR helpers. No VM/LLVM interaction here.
|
||||
|
||||
Rollback
|
||||
- Folder is isolated under lang/src/selfhost/. Removing this directory reverts to current behavior.
|
||||
|
||||
18
lang/src/selfhost/mir_builder/builder.hako
Normal file
18
lang/src/selfhost/mir_builder/builder.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// builder.hako — Self‑host MIR(JSON v0) Builder (Scaffold)
|
||||
// Phase‑20.12b: emit‑only placeholder. Returns input path unchanged.
|
||||
|
||||
static box SelfhostMirBuilder {
|
||||
// Build MIR(JSON v0) from AST/source (scaffold)
|
||||
// Returns: json_path (for now, identity)
|
||||
build(input_path) {
|
||||
// TODO(20.12b): parse/load, lower minimal ops (const/binop/compare/branch/ret/phi)
|
||||
return input_path
|
||||
}
|
||||
|
||||
// Future convenience: emit a minimal JSON header for parity checks
|
||||
emit_min_header(json_path) {
|
||||
// TODO(20.12b): write {"version":"0","kind":"Program"} header sidecar
|
||||
return json_path
|
||||
}
|
||||
}
|
||||
|
||||
17
lang/src/selfhost/mir_builder/phi.hako
Normal file
17
lang/src/selfhost/mir_builder/phi.hako
Normal file
@ -0,0 +1,17 @@
|
||||
// phi.hako — PHI helpers (Scaffold)
|
||||
// Phase‑20.12b: define shapes; real logic arrives in later steps.
|
||||
|
||||
static box SelfhostPhiBox {
|
||||
// Merge two inputs (placeholder)
|
||||
merge2(dst, in1, in2) {
|
||||
// TODO: construct MIR JSON node: {op:"phi", dst, inputs:[[bb1,v1],[bb2,v2]]}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Merge list of (bb,val) pairs (placeholder)
|
||||
mergeN(dst, inputs) {
|
||||
// TODO: validate inputs cover all predecessors
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
||||
10
lang/src/selfhost/mir_builder/verify.hako
Normal file
10
lang/src/selfhost/mir_builder/verify.hako
Normal file
@ -0,0 +1,10 @@
|
||||
// verify.hako — Selfhost MIR verifier (Scaffold)
|
||||
// Phase‑20.12b: minimal contract; always OK for now.
|
||||
|
||||
static box SelfhostMirVerify {
|
||||
verify(json_path) {
|
||||
// TODO: parse JSON and check: block start PHIs, pred coverage, no undefined uses
|
||||
return 0 // 0=ok (placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
8
lang/src/shared/README.md
Normal file
8
lang/src/shared/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
selfhost/shared — Shared boxes
|
||||
|
||||
Responsibilities
|
||||
- Minimal MIR schema/builders and JSON helpers used by compiler and VM.
|
||||
- No execution or parsing side effects.
|
||||
|
||||
Allowed imports
|
||||
- selfhost/shared/* only; no compiler/vm imports.
|
||||
37
lang/src/shared/adapters/map_kv_string_to_array.hako
Normal file
37
lang/src/shared/adapters/map_kv_string_to_array.hako
Normal file
@ -0,0 +1,37 @@
|
||||
// map_kv_string_to_array.hako — Adapter to convert keysS/valuesS string to ArrayBox
|
||||
// Responsibility: Provide split helpers (String -> ArrayBox) for MapBox keys/values
|
||||
|
||||
static box MapKvStringToArrayAdapter {
|
||||
// Split a newline-joined String into an ArrayBox of strings.
|
||||
// Empty or null-like input yields empty ArrayBox.
|
||||
split_lines(s) {
|
||||
local out = new ArrayBox()
|
||||
if s == null { return out }
|
||||
// Simple scan: split by "\n"
|
||||
local i = 0
|
||||
local start = 0
|
||||
local n = s.size()
|
||||
loop(i < n) {
|
||||
if s.substring(i, i+1) == "\n" {
|
||||
out.push(s.substring(start, i))
|
||||
start = i + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
// tail
|
||||
if start <= n { out.push(s.substring(start, n)) }
|
||||
return out
|
||||
}
|
||||
|
||||
// Adapt Map.keysS() -> ArrayBox
|
||||
adapt_keysS(map) {
|
||||
local ks = map.keysS()
|
||||
return me.split_lines(ks)
|
||||
}
|
||||
|
||||
// Adapt Map.valuesS() -> ArrayBox
|
||||
adapt_valuesS(map) {
|
||||
local vs = map.valuesS()
|
||||
return me.split_lines(vs)
|
||||
}
|
||||
}
|
||||
20
lang/src/shared/backend/llvm_backend_box.hako
Normal file
20
lang/src/shared/backend/llvm_backend_box.hako
Normal file
@ -0,0 +1,20 @@
|
||||
// LlvmBackendBox — Hako ABI (stub)
|
||||
// File-handoff only: compile MIR(JSON v0) to object, then link to EXE.
|
||||
// This is a stub for call-site wiring; current implementation is provided by external tools (ny-llvmc).
|
||||
|
||||
static box LlvmBackendBox {
|
||||
// Returns object path on success, or throws (Err) on failure.
|
||||
compile_obj(json_path) {
|
||||
// Stub only (dev): return a deterministic obj path to guide call sites.
|
||||
// Real implementation should shell out to ny-llvmc / harness.
|
||||
if json_path == null { throw "LlvmBackend.compile_obj: json_path is null" }
|
||||
local stem = json_path + ".o" // naive; callers should pass preferred path later
|
||||
return stem
|
||||
}
|
||||
|
||||
// Links an exe. Returns true on success (stub always false to prevent accidental use).
|
||||
link_exe(obj_path, out_path, libs) {
|
||||
if obj_path == null || out_path == null { throw "LlvmBackend.link_exe: path is null" }
|
||||
return false // stub
|
||||
}
|
||||
}
|
||||
121
lang/src/shared/common/box_helpers.hako
Normal file
121
lang/src/shared/common/box_helpers.hako
Normal file
@ -0,0 +1,121 @@
|
||||
// box_helpers.hako — 共通Box操作ヘルパー (Phase 31.2 共通化)
|
||||
// 目的: ArrayBox/MapBox の安全な操作を統一的に提供
|
||||
// 削減: 7ファイル × 2パターン = 14重複 → 1箇所に集約
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box BoxHelpers {
|
||||
// ArrayBox.size/1 の結果unwrap (MapBox-wrapped integer対応)
|
||||
array_len(arr) {
|
||||
if arr == null { return 0 }
|
||||
local size_val = call("ArrayBox.size/1", arr)
|
||||
local repr = "" + size_val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local inner = call("MapBox.get/2", size_val, "value")
|
||||
if inner != null { return inner }
|
||||
}
|
||||
return size_val
|
||||
}
|
||||
|
||||
// ArrayBox.get/2 の安全呼び出し
|
||||
array_get(arr, idx) {
|
||||
if arr == null { return null }
|
||||
return call("ArrayBox.get/2", arr, idx)
|
||||
}
|
||||
|
||||
// MapBox.get/2 の安全呼び出し
|
||||
map_get(obj, key) {
|
||||
if obj == null { return null }
|
||||
return call("MapBox.get/2", obj, key)
|
||||
}
|
||||
|
||||
// MapBox.set/3 の安全呼び出し(形だけ統一)
|
||||
map_set(obj, key, val) {
|
||||
if obj == null { obj = new MapBox() }
|
||||
call("MapBox.set/3", obj, key, val)
|
||||
return obj
|
||||
}
|
||||
|
||||
// MapBox-wrapped integer の unwrap (汎用版)
|
||||
value_i64(val) {
|
||||
if val == null { return 0 }
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local inner = call("MapBox.get/2", val, "value")
|
||||
if inner != null { return inner }
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MapBox型判定
|
||||
is_map(val) {
|
||||
if val == null { return 0 }
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
// ArrayBox型判定
|
||||
is_array(val) {
|
||||
if val == null { return 0 }
|
||||
local repr = "" + val
|
||||
if repr.indexOf("ArrayBox(") == 0 { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
// Fail-fast helpers (Phase 31.3+)
|
||||
expect_map(val, context) {
|
||||
if val == null {
|
||||
print("[BoxHelpers] expected MapBox for " + context + " but got null")
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_map_null")
|
||||
return val
|
||||
}
|
||||
if me.is_map(val) == 1 { return val }
|
||||
print("[BoxHelpers] dev assert failed: expected MapBox for " + context)
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_map")
|
||||
return val
|
||||
}
|
||||
|
||||
expect_array(val, context) {
|
||||
if val == null {
|
||||
print("[BoxHelpers] expected ArrayBox for " + context + " but got null")
|
||||
call("ArrayBox.get/2", val, 0)
|
||||
return val
|
||||
}
|
||||
if me.is_array(val) == 1 { return val }
|
||||
print("[BoxHelpers] dev assert failed: expected ArrayBox for " + context)
|
||||
call("ArrayBox.get/2", val, 0)
|
||||
return val
|
||||
}
|
||||
|
||||
expect_i64(val, context) {
|
||||
if val == null {
|
||||
print("[BoxHelpers] dev assert failed: expected i64 (non-null) for " + context)
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_i64_null")
|
||||
return 0
|
||||
}
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local ty = call("MapBox.get/2", val, "type")
|
||||
if ty != null {
|
||||
local ty_str = "" + ty
|
||||
if ty_str != "i64" && ty_str != "int" && ty_str != "integer" {
|
||||
print("[BoxHelpers] dev assert failed: unexpected type " + ty_str + " in " + context)
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_i64_type")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
local inner = call("MapBox.get/2", val, "value")
|
||||
if inner != null { return StringHelpers.to_i64(inner) }
|
||||
print("[BoxHelpers] dev assert failed: missing value in " + context)
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_i64_value")
|
||||
return 0
|
||||
}
|
||||
if StringHelpers.is_numeric_str("" + val) == 1 { return StringHelpers.to_i64(val) }
|
||||
print("[BoxHelpers] dev assert failed: expected numeric value for " + context)
|
||||
call("MapBox.get/2", val, "__box_helpers_expect_i64_direct")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
static box BoxHelpersStub { main(args) { return 0 } }
|
||||
276
lang/src/shared/common/mini_vm_binop.hako
Normal file
276
lang/src/shared/common/mini_vm_binop.hako
Normal file
@ -0,0 +1,276 @@
|
||||
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonCur
|
||||
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
|
||||
|
||||
static box MiniVmBinOp {
|
||||
// Minimal: Print(BinaryOp) with operator "+"; supports string+string and int+int
|
||||
try_print_binop_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = scan.index_of_from(json, k_bo, print_pos)
|
||||
if bpos <= 0 || bpos >= end { return -1 }
|
||||
// bound BinaryOp object (prefer expression object)
|
||||
local k_expr = "\"expression\":{"
|
||||
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
|
||||
local obj_start = -1
|
||||
if expr_pos > 0 && expr_pos < end {
|
||||
obj_start = scan.index_of_from(json, "{", expr_pos)
|
||||
} else {
|
||||
obj_start = scan.index_of_from(json, "{", bpos)
|
||||
}
|
||||
local obj_end = scan.find_balanced_object_end(json, obj_start)
|
||||
if obj_start <= 0 || obj_end <= 0 || obj_end > end { return -1 }
|
||||
// operator must be '+'
|
||||
local k_op = "\"operator\":\"+\""
|
||||
local opos = scan.index_of_from(json, k_op, bpos)
|
||||
if opos <= 0 || opos >= obj_end { return -1 }
|
||||
|
||||
// string + string fast-path
|
||||
local cur = new MiniJson()
|
||||
local k_left_lit = "\"left\":{\"kind\":\"Literal\""
|
||||
local lhdr = scan.index_of_from(json, k_left_lit, opos)
|
||||
if lhdr > 0 && lhdr < obj_end {
|
||||
local k_sval = "\"value\":\""
|
||||
local lvp = scan.index_of_from(json, k_sval, lhdr)
|
||||
if lvp > 0 && lvp < obj_end {
|
||||
local li = lvp + k_sval.size()
|
||||
local lval = cur.read_quoted_from(json, li)
|
||||
if lval {
|
||||
local k_right_lit = "\"right\":{\"kind\":\"Literal\""
|
||||
local rhdr = scan.index_of_from(json, k_right_lit, li + lval.size())
|
||||
if rhdr > 0 && rhdr < obj_end {
|
||||
local rvp = scan.index_of_from(json, k_sval, rhdr)
|
||||
if rvp > 0 && rvp < obj_end {
|
||||
local ri = rvp + k_sval.size()
|
||||
local rval = cur.read_quoted_from(json, ri)
|
||||
if rval { print(lval + rval) return ri + rval.size() + 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// int + int typed pattern
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\""
|
||||
local lpos = scan.index_of_from(json, k_l, opos)
|
||||
if lpos <= 0 || lpos >= obj_end { return -1 }
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local li2 = scan.index_of_from(json, k_lint, opos)
|
||||
if li2 <= 0 || li2 >= obj_end { return -1 }
|
||||
local ldigits = scan.read_digits(json, li2 + k_lint.size())
|
||||
if ldigits == "" { return -1 }
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\""
|
||||
local rpos = scan.index_of_from(json, k_r, lpos)
|
||||
if rpos <= 0 || rpos >= obj_end { return -1 }
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local ri2 = scan.index_of_from(json, k_rint, lpos)
|
||||
if ri2 <= 0 || ri2 >= obj_end { return -1 }
|
||||
local rdigits = scan.read_digits(json, ri2 + k_rint.size())
|
||||
if rdigits == "" { return -1 }
|
||||
local ai = scan._str_to_int(ldigits)
|
||||
local bi = scan._str_to_int(rdigits)
|
||||
print(scan._int_to_str(ai + bi))
|
||||
return obj_end + 1
|
||||
}
|
||||
|
||||
// Greedy disabled (kept for parity)
|
||||
try_print_binop_int_greedy(json, end, print_pos) { return -1 }
|
||||
|
||||
// Fallback: within the current Print's expression BinaryOp object, scan for two numeric values and sum
|
||||
try_print_binop_sum_any(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_expr = "\"expression\":{"
|
||||
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
|
||||
// If expression object cannot be bounded, fall back to typed-direct pattern within the current slice
|
||||
if expr_pos <= 0 || expr_pos >= end {
|
||||
// bound coarse slice to current Print by next Print marker
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local next_p = scan.index_of_from(json, k_print, print_pos + 1)
|
||||
local slice_end = end
|
||||
if next_p > 0 { slice_end = next_p }
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
// only if BinaryOp is present in this slice
|
||||
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", print_pos) > 0 {
|
||||
local lp = scan.index_of_from(json, k_lint, print_pos)
|
||||
if lp > 0 { if lp < slice_end {
|
||||
local ld = scan.read_digits(json, lp + k_lint.size())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
|
||||
if rp > 0 { if rp < slice_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.size())
|
||||
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
local obj_start = scan.index_of_from(json, "{", expr_pos)
|
||||
if obj_start <= 0 || obj_start >= end { return -1 }
|
||||
local obj_end = scan.find_balanced_object_end(json, obj_start)
|
||||
if obj_end <= 0 || obj_end > end {
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local next_p = scan.index_of_from(json, k_print, print_pos + 1)
|
||||
local obj_end2 = end
|
||||
if next_p > 0 && next_p <= end { obj_end2 = next_p }
|
||||
// typed-direct fallback in bounded region
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_lint, obj_start)
|
||||
if lp > 0 { if lp < obj_end2 {
|
||||
local ld = scan.read_digits(json, lp + k_lint.size())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
|
||||
if rp > 0 { if rp < obj_end2 {
|
||||
local rd = scan.read_digits(json, rp + k_rint.size())
|
||||
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
return -1
|
||||
}
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = scan.index_of_from(json, k_bo, obj_start)
|
||||
if bpos <= 0 || bpos >= obj_end {
|
||||
// typed-direct fallback (within bounds window)
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_lint, obj_start)
|
||||
if lp > 0 { if lp < obj_end {
|
||||
local ld = scan.read_digits(json, lp + k_lint.size())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.size())
|
||||
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
return -1
|
||||
}
|
||||
local k_plus = "\"operator\":\"+\""
|
||||
local opos = scan.index_of_from(json, k_plus, bpos)
|
||||
if opos <= 0 || opos >= obj_end {
|
||||
// typed-direct fallback
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_lint, obj_start)
|
||||
if lp > 0 { if lp < obj_end {
|
||||
local ld = scan.read_digits(json, lp + k_lint.size())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.size())
|
||||
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
return -1
|
||||
}
|
||||
local nums = []
|
||||
local i = obj_start
|
||||
loop (i < obj_end) {
|
||||
if call("String.substring/2", json, i, i+1) == "\"" {
|
||||
local j = scan.index_of_from(json, "\"", i+1)
|
||||
if j < 0 || j >= obj_end { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
local d = scan.read_digits(json, i)
|
||||
if d { nums.push(d) i = i + d.size() continue }
|
||||
i = i + 1
|
||||
}
|
||||
local nsz = nums.size()
|
||||
if nsz < 2 { return -1 }
|
||||
local a = scan._str_to_int(nums.get(nsz-2))
|
||||
local b = scan._str_to_int(nums.get(nsz-1))
|
||||
print(scan._int_to_str(a + b))
|
||||
return obj_end + 1
|
||||
}
|
||||
|
||||
// Deterministic: within Print.expression BinaryOp('+'), pick two successive 'value' fields and sum
|
||||
try_print_binop_sum_expr_values(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local cur = new MiniJson()
|
||||
local k_expr = "\"expression\":{"
|
||||
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
|
||||
if expr_pos <= 0 || expr_pos >= end { return -1 }
|
||||
local obj_start = scan.index_of_from(json, "{", expr_pos)
|
||||
if obj_start <= 0 || obj_start >= end { return -1 }
|
||||
local obj_end = scan.find_balanced_object_end(json, obj_start)
|
||||
if obj_end <= 0 || obj_end > end { return -1 }
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = scan.index_of_from(json, k_bo, obj_start)
|
||||
if bpos <= 0 || bpos >= obj_end { return -1 }
|
||||
local k_plus = "\"operator\":\"+\""
|
||||
local opos = scan.index_of_from(json, k_plus, bpos)
|
||||
if opos <= 0 || opos >= obj_end { return -1 }
|
||||
local k_v = "\"value\":"
|
||||
local found = 0
|
||||
local a = 0
|
||||
local pos = scan.index_of_from(json, k_v, obj_start)
|
||||
loop (pos > 0 && pos < obj_end) {
|
||||
local di = cur.read_digits_from(json, pos + k_v.size())
|
||||
if di != "" {
|
||||
if found == 0 { a = scan._str_to_int(di) found = 1 } else {
|
||||
local b = scan._str_to_int(di)
|
||||
print(scan._int_to_str(a + b))
|
||||
return obj_end + 1
|
||||
}
|
||||
}
|
||||
pos = scan.index_of_from(json, k_v, pos + k_v.size())
|
||||
if pos <= 0 || pos >= obj_end { break }
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Simpler: after operator '+', scan two successive 'value' fields and sum
|
||||
try_print_binop_sum_after_bop(json) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos < 0 { return -1 }
|
||||
local k_plus = "\"operator\":\"+\""
|
||||
local opos = scan.index_of_from(json, k_plus, bpos)
|
||||
if opos < 0 { return -1 }
|
||||
local k_v = "\"value\":"
|
||||
local p = opos
|
||||
p = scan.index_of_from(json, k_v, p)
|
||||
if p < 0 { return -1 }
|
||||
p = scan.index_of_from(json, k_v, p + k_v.size())
|
||||
if p < 0 { return -1 }
|
||||
local end1 = scan.index_of_from(json, "}", p)
|
||||
if end1 < 0 { return -1 }
|
||||
local d1 = scan.read_digits(json, p + k_v.size())
|
||||
if d1 == "" { return -1 }
|
||||
p = scan.index_of_from(json, k_v, end1)
|
||||
if p < 0 { return -1 }
|
||||
p = scan.index_of_from(json, k_v, p + k_v.size())
|
||||
if p < 0 { return -1 }
|
||||
local end2 = scan.index_of_from(json, "}", p)
|
||||
if end2 < 0 { return -1 }
|
||||
local d2 = scan.read_digits(json, p + k_v.size())
|
||||
if d2 == "" { return -1 }
|
||||
local ai = scan._str_to_int(d1)
|
||||
local bi = scan._str_to_int(d2)
|
||||
print(scan._int_to_str(ai + bi))
|
||||
return 0
|
||||
}
|
||||
|
||||
// Fallback: find first BinaryOp and return sum of two numeric values as string
|
||||
parse_first_binop_sum(json) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos < 0 { return "" }
|
||||
local k_typed = "\"type\":\"int\",\"value\":"
|
||||
local p1 = scan.index_of_from(json, k_typed, bpos)
|
||||
if p1 < 0 { return "" }
|
||||
local d1 = scan.read_digits(json, p1 + k_typed.size())
|
||||
if d1 == "" { return "" }
|
||||
local p2 = scan.index_of_from(json, k_typed, p1 + k_typed.size())
|
||||
if p2 < 0 { return "" }
|
||||
local d2 = scan.read_digits(json, p2 + k_typed.size())
|
||||
if d2 == "" { return "" }
|
||||
return scan._int_to_str(scan._str_to_int(d1) + scan._str_to_int(d2))
|
||||
}
|
||||
}
|
||||
49
lang/src/shared/common/mini_vm_compare.hako
Normal file
49
lang/src/shared/common/mini_vm_compare.hako
Normal file
@ -0,0 +1,49 @@
|
||||
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
|
||||
|
||||
static box MiniVmCompare {
|
||||
// Compare(lhs int, rhs int) minimal: prints 0/1 and returns next pos or -1
|
||||
try_print_compare_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = scan.indexOf(k_cp)
|
||||
if cpos < 0 { cpos = scan.index_of_from(json, k_cp, print_pos) }
|
||||
if cpos <= 0 || cpos >= end { return -1 }
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = scan.index_of_from(json, k_op, cpos)
|
||||
if opos <= 0 || opos >= end { return -1 }
|
||||
local oi = opos + k_op.size()
|
||||
local oj = scan.index_of_from(json, "\"", oi)
|
||||
if oj <= 0 || oj > end { return -1 }
|
||||
local op = json.substring(oi, oj)
|
||||
// lhs value
|
||||
local k_lhs = "\"lhs\":{\"kind\":\"Literal\""
|
||||
local hl = scan.index_of_from(json, k_lhs, oj)
|
||||
if hl <= 0 || hl >= end { return -1 }
|
||||
local k_v = "\"value\":"
|
||||
local hv = scan.index_of_from(json, k_v, hl)
|
||||
if hv <= 0 || hv >= end { return -1 }
|
||||
local a = scan.read_digits(json, hv + k_v.size())
|
||||
// rhs value
|
||||
local k_rhs = "\"rhs\":{\"kind\":\"Literal\""
|
||||
local hr = scan.index_of_from(json, k_rhs, hl)
|
||||
if hr <= 0 || hr >= end { return -1 }
|
||||
local rv = scan.index_of_from(json, k_v, hr)
|
||||
if rv <= 0 || rv >= end { return -1 }
|
||||
local b = scan.read_digits(json, rv + k_v.size())
|
||||
if !a || !b { return -1 }
|
||||
local ai = scan._str_to_int(a)
|
||||
local bi = scan._str_to_int(b)
|
||||
local res = match op {
|
||||
"<" => { if ai < bi { 1 } else { 0 } }
|
||||
"==" => { if ai == bi { 1 } else { 0 } }
|
||||
"<=" => { if ai <= bi { 1 } else { 0 } }
|
||||
">" => { if ai > bi { 1 } else { 0 } }
|
||||
">=" => { if ai >= bi { 1 } else { 0 } }
|
||||
"!=" => { if ai != bi { 1 } else { 0 } }
|
||||
_ => 0
|
||||
}
|
||||
print(res)
|
||||
// advance after rhs object (coarsely)
|
||||
return rv + 1
|
||||
}
|
||||
}
|
||||
79
lang/src/shared/common/mini_vm_scan.hako
Normal file
79
lang/src/shared/common/mini_vm_scan.hako
Normal file
@ -0,0 +1,79 @@
|
||||
// Mini-VM scanning and numeric helpers
|
||||
// Delegation Policy: all scanning primitives route to JsonCursorBox to avoid divergence.
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box MiniVmScan {
|
||||
// helper: find needle from position pos (escape-aware where needed)
|
||||
index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
|
||||
|
||||
// helper: find balanced bracket range [ ... ] starting at idx (points to '[')
|
||||
find_balanced_array_end(json, idx) { return JsonCursorBox.seek_array_end(json, idx) }
|
||||
|
||||
// helper: find balanced object range { ... } starting at idx (points to '{')
|
||||
find_balanced_object_end(json, idx) { return JsonCursorBox.seek_obj_end(json, idx) }
|
||||
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
read_digits(json, pos) { return StringHelpers.read_digits(json, pos) }
|
||||
|
||||
// Linear pass: sum all numbers outside of quotes
|
||||
sum_numbers_no_quotes(json) {
|
||||
@i = 0
|
||||
@n = json.size()
|
||||
@total = 0
|
||||
loop (i < n) {
|
||||
@ch = call("String.substring/2", json, i, i+1)
|
||||
if ch == "\"" {
|
||||
@j = me.index_of_from(json, "\"", i+1)
|
||||
if j < 0 { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
@d = me.read_digits(json, i)
|
||||
if d { total = total + me._str_to_int(d) i = i + d.size() continue }
|
||||
i = i + 1
|
||||
}
|
||||
return me._int_to_str(total)
|
||||
}
|
||||
|
||||
// Naive: sum all digit runs anywhere
|
||||
sum_all_digits_naive(json) {
|
||||
@i = 0
|
||||
@n = json.size()
|
||||
@total = 0
|
||||
loop (i < n) {
|
||||
@d = me.read_digits(json, i)
|
||||
if d { total = total + me._str_to_int(d) i = i + d.size() continue }
|
||||
i = i + 1
|
||||
}
|
||||
return me._int_to_str(total)
|
||||
}
|
||||
|
||||
// Sum first two integers outside quotes; returns string or empty
|
||||
sum_first_two_numbers(json) {
|
||||
@i = 0
|
||||
@n = json.size()
|
||||
@total = 0
|
||||
local found = 0
|
||||
loop (i < n) {
|
||||
@ch = call("String.substring/2", json, i, i+1)
|
||||
if ch == "\"" {
|
||||
@j = me.index_of_from(json, "\"", i+1)
|
||||
if j < 0 { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
@d = me.read_digits(json, i)
|
||||
if d {
|
||||
total = total + me._str_to_int(d)
|
||||
found = found + 1
|
||||
i = i + d.size()
|
||||
if found >= 2 { return me._int_to_str(total) }
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
39
lang/src/shared/common/modules_inspect_box.hako
Normal file
39
lang/src/shared/common/modules_inspect_box.hako
Normal file
@ -0,0 +1,39 @@
|
||||
// modules_inspect_box.hako — ModulesInspectBox
|
||||
// Responsibility: Helpers for Dir-as-NS module inspection from Nyash code.
|
||||
// Non‑IO, formatting-only utilities. Caller provides inputs (e.g., lists or JSON).
|
||||
|
||||
static box ModulesInspectBox {
|
||||
// Convert apps-relative path to namespace (Dir-as-NS v2.2 rules)
|
||||
path_to_namespace(path) {
|
||||
if path == null { return "" }
|
||||
local s = "" + path
|
||||
// Strip leading ./
|
||||
if s.size() >= 2 && s.substring(0,2) == "./" { s = s.substring(2, s.size()) }
|
||||
// Remove leading apps/
|
||||
local pref = "apps/"
|
||||
if s.size() >= pref.size() && s.substring(0, pref.size()) == pref { s = s.substring(pref.size(), s.size()) }
|
||||
// Replace '-' with '.' in directory parts only
|
||||
local out = ""
|
||||
local i = 0
|
||||
loop(i < s.size()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "/" { out = out + "." i = i + 1 continue }
|
||||
if ch == "-" { out = out + "." i = i + 1 continue }
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
// Drop .hako and optional _box suffix
|
||||
if out.size() >= 5 && out.substring(out.size()-5, out.size()) == ".hako" {
|
||||
out = out.substring(0, out.size()-5)
|
||||
}
|
||||
if out.size() >= 4 && out.substring(out.size()-4, out.size()) == "_box" {
|
||||
out = out.substring(0, out.size()-4)
|
||||
}
|
||||
return out
|
||||
}
|
||||
// Pretty single line
|
||||
pretty_line(tag, ns, path) { return "[" + tag + "] " + ns + " → " + path }
|
||||
}
|
||||
|
||||
static box ModulesInspectMain { main(args){ return 0 } }
|
||||
|
||||
174
lang/src/shared/common/string_helpers.hako
Normal file
174
lang/src/shared/common/string_helpers.hako
Normal file
@ -0,0 +1,174 @@
|
||||
// string_helpers.hako — StringHelpers (common, pure helpers)
|
||||
// Responsibility: numeric/string conversions and JSON quoting for selfhost tools.
|
||||
// Non-responsibility: JSON scanning beyond local character processing; use CfgNavigatorBox/StringScanBox for navigation.
|
||||
|
||||
static box StringHelpers {
|
||||
// Convert a numeric or numeric-like string to its decimal representation.
|
||||
int_to_str(n) {
|
||||
local v = me.to_i64(n)
|
||||
if v == 0 { return "0" }
|
||||
if v < 0 { return "-" + me.int_to_str(0 - v) }
|
||||
local out = ""
|
||||
local digits = "0123456789"
|
||||
loop (v > 0) {
|
||||
local d = v % 10
|
||||
local ch = digits.substring(d, d+1)
|
||||
out = ch + out
|
||||
v = v / 10
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Parse integer from number or numeric-like string (leading '-' allowed; stops at first non-digit).
|
||||
to_i64(x) {
|
||||
local s = "" + x
|
||||
local i = 0
|
||||
local neg = 0
|
||||
if s.substring(0,1) == "-" { neg = 1 i = 1 }
|
||||
local n = s.size()
|
||||
if i >= n { return 0 }
|
||||
local acc = 0
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch < "0" || ch > "9" { break }
|
||||
local ds = "0123456789"
|
||||
local dpos = ds.indexOf(ch)
|
||||
if dpos < 0 { break }
|
||||
acc = acc * 10 + dpos
|
||||
i = i + 1
|
||||
}
|
||||
if neg == 1 { return 0 - acc }
|
||||
return acc
|
||||
}
|
||||
|
||||
// Quote a string for JSON (escape backslash, quote, and control chars) and wrap with quotes
|
||||
json_quote(s) {
|
||||
if s == null { return "\"\"" }
|
||||
local out = ""
|
||||
local i = 0
|
||||
local n = s.size()
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "\\" { out = out + "\\\\" }
|
||||
else { if ch == "\"" { out = out + "\\\"" } else {
|
||||
if ch == "\n" { out = out + "\\n" } else {
|
||||
if ch == "\r" { out = out + "\\r" } else {
|
||||
if ch == "\t" { out = out + "\\t" } else { out = out + ch }
|
||||
}
|
||||
}
|
||||
}}
|
||||
i = i + 1
|
||||
}
|
||||
return "\"" + out + "\""
|
||||
}
|
||||
|
||||
// Check if string is numeric-like (optional leading '-', then digits).
|
||||
is_numeric_str(s) {
|
||||
if s == null { return 0 }
|
||||
local n = s.size()
|
||||
if n == 0 { return 0 }
|
||||
local i = 0
|
||||
if s.substring(0,1) == "-" { if n == 1 { return 0 } i = 1 }
|
||||
loop(i < n) { local ch = s.substring(i, i+1) if ch < "0" || ch > "9" { return 0 } i = i + 1 }
|
||||
return 1
|
||||
}
|
||||
|
||||
// Read consecutive digits starting at pos (no sign handling here; keep semantics simple)
|
||||
read_digits(text, pos) {
|
||||
local out = ""
|
||||
loop (true) {
|
||||
local ch = text.substring(pos, pos+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch pos = pos + 1 } else { break }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// 🆕 Extended String Utilities (added for parser/compiler unification)
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
// Character predicates
|
||||
is_digit(ch) { return ch >= "0" && ch <= "9" }
|
||||
is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
|
||||
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
|
||||
|
||||
// Pattern matching
|
||||
starts_with(src, i, pat) {
|
||||
local n = src.size()
|
||||
local m = pat.size()
|
||||
if i + m > n { return 0 }
|
||||
local k = 0
|
||||
loop(k < m) {
|
||||
if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 }
|
||||
k = k + 1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// Keyword match with word boundary (next char not [A-Za-z0-9_])
|
||||
starts_with_kw(src, i, kw) {
|
||||
if me.starts_with(src, i, kw) == 0 { return 0 }
|
||||
local n = src.size()
|
||||
local j = i + kw.size()
|
||||
if j >= n { return 1 }
|
||||
local ch = src.substring(j, j+1)
|
||||
if me.is_alpha(ch) || me.is_digit(ch) { return 0 }
|
||||
return 1
|
||||
}
|
||||
|
||||
// String search
|
||||
index_of(src, i, pat) {
|
||||
local n = src.size()
|
||||
local m = pat.size()
|
||||
if m == 0 { return i }
|
||||
local j = i
|
||||
loop(j + m <= n) {
|
||||
if me.starts_with(src, j, pat) { return j }
|
||||
j = j + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Trim spaces and tabs (with optional semicolon at end)
|
||||
trim(s) {
|
||||
local i = 0
|
||||
local n = s.size()
|
||||
loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 }
|
||||
local j = n
|
||||
loop(j > i && (s.substring(j-1,j) == " " || s.substring(j-1,j) == "\t" || s.substring(j-1,j) == ";")) { j = j - 1 }
|
||||
return s.substring(i, j)
|
||||
}
|
||||
|
||||
// Skip whitespace from position i
|
||||
skip_ws(src, i) {
|
||||
if src == null { return i }
|
||||
local n = src.size()
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 100000
|
||||
loop(cont == 1) {
|
||||
if guard > max { return i } else { guard = guard + 1 }
|
||||
if i < n {
|
||||
if me.is_space(src.substring(i, i+1)) { i = i + 1 } else { cont = 0 }
|
||||
} else { cont = 0 }
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Find last occurrence of pattern in string (backward search)
|
||||
last_index_of(src, pat) {
|
||||
if src == null { return -1 }
|
||||
if pat == null { return -1 }
|
||||
local n = src.size()
|
||||
local m = pat.size()
|
||||
if m == 0 { return n }
|
||||
if m > n { return -1 }
|
||||
local i = n - m
|
||||
loop(i >= 0) {
|
||||
if me.starts_with(src, i, pat) { return i }
|
||||
i = i - 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
23
lang/src/shared/common/string_ops.hako
Normal file
23
lang/src/shared/common/string_ops.hako
Normal file
@ -0,0 +1,23 @@
|
||||
// string_ops.hako — Common string utility functions
|
||||
// Responsibility: Basic string operations used across the codebase
|
||||
// Non-goals: JSON parsing, complex text processing
|
||||
|
||||
static box StringOps {
|
||||
// Find substring starting from position
|
||||
// Returns: index of first occurrence at/after pos, or -1 if not found
|
||||
index_of_from(text, needle, pos) {
|
||||
if text == null { return -1 }
|
||||
if pos < 0 { pos = 0 }
|
||||
local n = text.size()
|
||||
if pos >= n { return -1 }
|
||||
local m = needle.size()
|
||||
if m <= 0 { return pos }
|
||||
local i = pos
|
||||
local limit = n - m
|
||||
loop (i <= limit) {
|
||||
if text.substring(i, i + m) == needle { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
32
lang/src/shared/hako_module.toml
Normal file
32
lang/src/shared/hako_module.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[module]
|
||||
name = "selfhost.shared"
|
||||
version = "1.0.0"
|
||||
|
||||
[exports]
|
||||
# JSON adapter facade and common helpers
|
||||
json_adapter = "json_adapter.hako"
|
||||
common.mini_vm_scan = "common/mini_vm_scan.hako"
|
||||
common.mini_vm_binop = "common/mini_vm_binop.hako"
|
||||
common.mini_vm_compare = "common/mini_vm_compare.hako"
|
||||
common.string_helpers = "common/string_helpers.hako"
|
||||
common.string_ops = "common/string_ops.hako"
|
||||
common.box_helpers = "common/box_helpers.hako"
|
||||
|
||||
# JSON tooling
|
||||
json.mir_builder_min = "json/mir_builder_min.hako"
|
||||
json.mir_v1_adapter = "json/mir_v1_adapter.hako"
|
||||
json.core.json_cursor = "json/json_cursor.hako"
|
||||
json.utils.json_utils = "json/json_utils.hako"
|
||||
|
||||
# MIR helpers (exported as stable module names)
|
||||
mir.schema = "mir/mir_schema_box.hako"
|
||||
mir.builder = "mir/block_builder_box.hako"
|
||||
mir.io = "mir/mir_io_box.hako"
|
||||
mir.json_emit = "mir/json_emit_box.hako"
|
||||
|
||||
[private]
|
||||
# Internal builders kept private for now
|
||||
# json/mir_builder2.hako, json/json_inst_encode_box.hako
|
||||
|
||||
[dependencies]
|
||||
"selfhost.vm" = "^1.0.0"
|
||||
21
lang/src/shared/host_bridge/host_bridge_box.hako
Normal file
21
lang/src/shared/host_bridge/host_bridge_box.hako
Normal file
@ -0,0 +1,21 @@
|
||||
// host_bridge_box.hako — HostBridgeBox (Phase A, Hako-side thin adapter)
|
||||
// Responsibility: Provide minimal API to construct/call host boxes for VM backend.
|
||||
// Note: Phase A supports only a small subset (FileBox). Phase B wires full Hako ABI.
|
||||
|
||||
static box HostBridgeBox {
|
||||
// Create a box by name with args (Phase B: extern → Rust HostBridge)
|
||||
// Route: hostbridge.box_new(name, args)
|
||||
box_new(name, args) {
|
||||
if args == null { args = new ArrayBox() }
|
||||
return hostbridge.box_new(name, args)
|
||||
}
|
||||
|
||||
// Call a method by name with Array args (Phase B: extern → Rust HostBridge)
|
||||
// Route: hostbridge.box_call(receiver, method, args) → lowered to Extern("hostbridge.box_call")
|
||||
box_call(inst, method, args) {
|
||||
if inst == null { return null }
|
||||
if args == null { args = new ArrayBox() }
|
||||
return hostbridge.box_call(inst, method, args)
|
||||
}
|
||||
}
|
||||
|
||||
105
lang/src/shared/json/core/json_scan.hako
Normal file
105
lang/src/shared/json/core/json_scan.hako
Normal file
@ -0,0 +1,105 @@
|
||||
// json_scan.hako — JsonScanBox
|
||||
// Escape-aware JSON structure scanning helpers.
|
||||
|
||||
using "lang/src/shared/json/core/string_scan.hako" as StringScanBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box JsonScanBox {
|
||||
// minimal string→int (delegate to shared helper)
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
// Seek the end index (inclusive) of an object starting at `start` (must be '{').
|
||||
seek_obj_end(text, start) {
|
||||
if text == null { return -1 }
|
||||
if start < 0 || start >= text.size() { return -1 }
|
||||
if call("String.substring/2", text, start, start+1) != "{" { return -1 }
|
||||
local n = text.size()
|
||||
local depth = 0
|
||||
local i = start
|
||||
local in_str = 0
|
||||
loop (i < n) {
|
||||
local ch = StringScanBox.read_char(text, i)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" {
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
if ch == "\"" {
|
||||
in_str = 0
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" {
|
||||
local j = StringScanBox.find_quote(text, i+1)
|
||||
if j < 0 { return -1 }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
if ch == "{" {
|
||||
depth = depth + 1
|
||||
} else if ch == "}" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return i }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Seek the end index (inclusive) of an array starting at `start` (must be '[').
|
||||
seek_array_end(text, start) {
|
||||
if text == null { return -1 }
|
||||
// Normalize start to integer (defensive against stringified input)
|
||||
local start_s = "" + start
|
||||
local start_i = me._str_to_int(start_s)
|
||||
local n = text.size()
|
||||
if start_i < 0 || start_i >= n { return -1 }
|
||||
local first_ch = text.substring(start_i, start_i+1)
|
||||
if first_ch != "[" { return -1 }
|
||||
// main loop (escape-aware)
|
||||
local depth = 0
|
||||
local i = start_i
|
||||
local in_str = 0
|
||||
loop (i < n) {
|
||||
local ch = StringScanBox.read_char(text, i)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" {
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
if ch == "\"" {
|
||||
in_str = 0
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" {
|
||||
local j = StringScanBox.find_quote(text, i+1)
|
||||
if j < 0 { return -1 }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
if ch == "[" {
|
||||
depth = depth + 1
|
||||
} else if ch == "]" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return i }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Search for either plain or escaped key pattern starting at `pos`.
|
||||
find_key_dual(text, plain, escaped, pos) {
|
||||
if text == null { return -1 }
|
||||
if pos < 0 { pos = 0 }
|
||||
local p = StringScanBox.index_of_from(text, plain, pos)
|
||||
if p >= 0 { return p }
|
||||
return StringScanBox.index_of_from(text, escaped, pos)
|
||||
}
|
||||
}
|
||||
59
lang/src/shared/json/core/string_scan.hako
Normal file
59
lang/src/shared/json/core/string_scan.hako
Normal file
@ -0,0 +1,59 @@
|
||||
// string_scan.hako — StringScanBox
|
||||
// Escape-aware string scanning helpers for JSON/text processing.
|
||||
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
|
||||
static box StringScanBox {
|
||||
// Return the single-character string at index i (empty if out of bounds)
|
||||
read_char(text, i) {
|
||||
if text == null { return "" }
|
||||
if i < 0 { return "" }
|
||||
local n = text.size()
|
||||
if i >= n { return "" }
|
||||
return text.substring(i, i+1)
|
||||
}
|
||||
// Find next unescaped double quote starting at or after pos
|
||||
find_quote(text, pos) {
|
||||
return me.find_unescaped(text, "\"", pos)
|
||||
}
|
||||
// Find substring starting from position (simple, non-escape-aware)
|
||||
index_of_from(text, needle, pos) {
|
||||
return StringOps.index_of_from(text, needle, pos)
|
||||
}
|
||||
// Find next occurrence of character `ch` in `text` at or after `pos`,
|
||||
// ignoring escaped occurrences (preceded by a backslash).
|
||||
find_unescaped(text, ch, pos) {
|
||||
if text == null { return -1 }
|
||||
if pos < 0 { pos = 0 }
|
||||
local n = text.size()
|
||||
local i = pos
|
||||
loop (i < n) {
|
||||
local c = me.read_char(text, i)
|
||||
if c == "\\" {
|
||||
// skip escape sequence
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
if c == ch { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Given a starting index at a double quote, find the closing quote index
|
||||
// respecting escape sequences; returns -1 if unterminated.
|
||||
scan_string_end(text, start) {
|
||||
if text == null { return -1 }
|
||||
if start < 0 || start >= text.size() { return -1 }
|
||||
if text.substring(start, start+1) != "\"" { return -1 }
|
||||
local i = start + 1
|
||||
local n = text.size()
|
||||
loop (i < n) {
|
||||
local c = me.read_char(text, i)
|
||||
if c == "\\" { i = i + 2 continue }
|
||||
if c == "\"" { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
12
lang/src/shared/json/json_canonical_box.hako
Normal file
12
lang/src/shared/json/json_canonical_box.hako
Normal file
@ -0,0 +1,12 @@
|
||||
// json_canonical_box.hako — JsonCanonicalBox (Phase 20.5 Gate A unification)
|
||||
// Responsibility: Provide a single entry to canonicalize JSON strings.
|
||||
// Current behavior: identity (no-op) until Extern anchor bridging is wired.
|
||||
// Future: call host anchor `nyash_json_canonicalize_h` via Extern/HostBridge to obtain canonical form.
|
||||
|
||||
static box JsonCanonicalBox {
|
||||
canonicalize(json) {
|
||||
// Phase-A: identity(host bridge未接続の間は常にno-op)
|
||||
// 期待動作: 文字列正規化は将来のExternに委譲。現状はそのまま返す。
|
||||
return "" + json
|
||||
}
|
||||
}
|
||||
44
lang/src/shared/json/json_cursor.hako
Normal file
44
lang/src/shared/json/json_cursor.hako
Normal file
@ -0,0 +1,44 @@
|
||||
// json_cursor.hako — JsonCursorBox (thin scan facade)
|
||||
// Responsibility: provide a minimal, escape-aware scanning facade used by JsonFragBox
|
||||
// Delegates to StringOps, StringScanBox and JsonScanBox.
|
||||
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
using "lang/src/shared/json/core/string_scan.hako" as StringScanBox
|
||||
using "lang/src/shared/json/core/json_scan.hako" as JsonScanBox
|
||||
|
||||
static box JsonCursorBox {
|
||||
index_of_from(hay, needle, pos) {
|
||||
return StringOps.index_of_from(hay, needle, pos)
|
||||
}
|
||||
// Alias: find_from (compat)
|
||||
find_from(hay, needle, pos) { return me.index_of_from(hay, needle, pos) }
|
||||
scan_string_end(text, quote_pos) { return StringScanBox.scan_string_end(text, quote_pos) }
|
||||
seek_array_end(text, lbracket_pos) { return JsonScanBox.seek_array_end(text, lbracket_pos) }
|
||||
seek_obj_end(text, start) { return JsonScanBox.seek_obj_end(text, start) }
|
||||
find_key_dual(text, plain, escaped, pos) { return JsonScanBox.find_key_dual(text, plain, escaped, pos) }
|
||||
// Extract a sequence of optional sign + digits starting at start_pos
|
||||
digits_from(text, start_pos) {
|
||||
if text == null { return "" }
|
||||
local i = start_pos
|
||||
local n = text.size()
|
||||
local out = ""
|
||||
if i < n {
|
||||
local ch = text.substring(i, i+1)
|
||||
if ch == "-" {
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
loop(i < n) {
|
||||
local ch = text.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" {
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
} else { break }
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
static box JsonCursorMain { main(args){ return 0 } }
|
||||
25
lang/src/shared/json/json_inst_encode_box.hako
Normal file
25
lang/src/shared/json/json_inst_encode_box.hako
Normal file
@ -0,0 +1,25 @@
|
||||
// json_inst_encode_box.hako — Encode one MIR instruction Map -> JSON text (single return)
|
||||
|
||||
static box JsonInstEncodeBox {
|
||||
_i(n) { return "" + n }
|
||||
_q(s) { return s }
|
||||
|
||||
encode(node) {
|
||||
if node == null { return "{}" }
|
||||
local op = node.get("op")
|
||||
return match op {
|
||||
"const" => "{\"op\":\"const\",\"dst\":" + me._i(node.get("dst")) + ",\"value\":{\"type\":\"i64\",\"value\":" + me._i(node.get("value").get("value")) + "}}"
|
||||
"compare" => "{\"op\":\"compare\",\"cmp\":" + me._q(node.get("cmp")) + ",\"lhs\":" + me._i(node.get("lhs")) + ",\"rhs\":" + me._i(node.get("rhs")) + ",\"dst\":" + me._i(node.get("dst")) + "}"
|
||||
"binop" => "{\"op\":\"binop\",\"op_kind\":" + me._q(node.get("op_kind")) + ",\"lhs\":" + me._i(node.get("lhs")) + ",\"rhs\":" + me._i(node.get("rhs")) + ",\"dst\":" + me._i(node.get("dst")) + "}"
|
||||
"branch" => "{\"op\":\"branch\",\"cond\":" + me._i(node.get("cond")) + ",\"then\":" + me._i(node.get("then")) + ",\"else\":" + me._i(node.get("else_id")) + "}"
|
||||
"jump" => "{\"op\":\"jump\",\"target\":" + me._i(node.get("target")) + "}"
|
||||
"ret" => "{\"op\":\"ret\",\"value\":" + me._i(node.get("value")) + "}"
|
||||
"copy" => "{\"op\":\"copy\",\"dst\":" + me._i(node.get("dst")) + ",\"src\":" + me._i(node.get("src")) + "}"
|
||||
"call" => "{\"op\":\"call\",\"name\":" + me._q(node.get("name")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
|
||||
"boxcall" => "{\"op\":\"boxcall\",\"method\":" + me._q(node.get("method")) + ",\"recv\":" + me._i(node.get("recv")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
|
||||
"newbox" => "{\"op\":\"newbox\",\"box_type\":" + me._q(node.get("box_type")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
|
||||
"mir_call" => "{\"op\":\"mir_call\"}"
|
||||
_ => "{}"
|
||||
}
|
||||
}
|
||||
}
|
||||
211
lang/src/shared/json/json_utils.hako
Normal file
211
lang/src/shared/json/json_utils.hako
Normal file
@ -0,0 +1,211 @@
|
||||
// JsonUtilsBox — JSON読み取りユーティリティの共通箱
|
||||
// 責務: JSON値抽出・JSON構造パース・トップレベル配列分割
|
||||
// Extracted from JsonProgramBox (480行 → 345行, 削減 ~135行)
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box JsonUtilsBox {
|
||||
// Extract JSON value by key (returns value with '@' + end position marker)
|
||||
extract_value(json, key) {
|
||||
if json == null { return null }
|
||||
local pattern = "\"" + key + "\""
|
||||
local idx = StringHelpers.index_of(json, 0, pattern)
|
||||
if idx < 0 { return null }
|
||||
idx = idx + pattern.size()
|
||||
idx = StringHelpers.skip_ws(json, idx)
|
||||
if json.substring(idx, idx + 1) != ":" { return null }
|
||||
idx = StringHelpers.skip_ws(json, idx + 1)
|
||||
local res = me.read_value(json, idx)
|
||||
local at = StringHelpers.last_index_of(res, "@")
|
||||
if at < 0 { return null }
|
||||
return res.substring(0, at)
|
||||
}
|
||||
|
||||
// Extract string value by key with default fallback
|
||||
extract_string_value(json, key, default_value) {
|
||||
local raw = me.extract_value(json, key)
|
||||
if raw == null { return default_value }
|
||||
local trimmed = StringHelpers.trim(raw)
|
||||
if trimmed.size() >= 2 && trimmed.substring(0,1) == "\"" && trimmed.substring(trimmed.size()-1, trimmed.size()) == "\"" {
|
||||
return me.unescape_string(trimmed.substring(1, trimmed.size()-1))
|
||||
}
|
||||
return default_value
|
||||
}
|
||||
|
||||
// Read JSON value (dispatch to appropriate reader)
|
||||
read_value(json, idx) {
|
||||
local n = json.size()
|
||||
if idx >= n { return "@" + StringHelpers.int_to_str(idx) }
|
||||
local ch = json.substring(idx, idx + 1)
|
||||
if ch == "\"" { return me.read_string(json, idx) }
|
||||
if ch == "{" { return me.read_object(json, idx) }
|
||||
if ch == "[" { return me.read_array(json, idx) }
|
||||
return me.read_literal(json, idx)
|
||||
}
|
||||
|
||||
// Read JSON string (escape-aware) with position marker
|
||||
read_string(json, idx) {
|
||||
local i = idx + 1
|
||||
local n = json.size()
|
||||
local done = 0
|
||||
loop(done == 0 && i < n) {
|
||||
local ch = json.substring(i, i + 1)
|
||||
if ch == "\\" {
|
||||
i = i + 2
|
||||
} else {
|
||||
if ch == "\"" { done = 1 i = i + 1 } else { i = i + 1 }
|
||||
}
|
||||
}
|
||||
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
|
||||
}
|
||||
|
||||
// Skip JSON string (returns end position)
|
||||
skip_string(json, idx) {
|
||||
local i = idx + 1
|
||||
local n = json.size()
|
||||
local done = 0
|
||||
loop(done == 0 && i < n) {
|
||||
local ch = json.substring(i, i + 1)
|
||||
if ch == "\\" { i = i + 2 }
|
||||
else { if ch == "\"" { done = 1 i = i + 1 } else { i = i + 1 } }
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Read JSON object (bracket-aware) with position marker
|
||||
read_object(json, idx) {
|
||||
local depth = 0
|
||||
local i = idx
|
||||
local n = json.size()
|
||||
loop(i < n) {
|
||||
local ch = json.substring(i, i + 1)
|
||||
if ch == "\"" {
|
||||
i = me.skip_string(json, i)
|
||||
} else {
|
||||
if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { i = i + 1 break }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
|
||||
}
|
||||
|
||||
// Read JSON array (bracket-aware) with position marker
|
||||
read_array(json, idx) {
|
||||
local depth = 0
|
||||
local i = idx
|
||||
local n = json.size()
|
||||
loop(i < n) {
|
||||
local ch = json.substring(i, i + 1)
|
||||
if ch == "\"" {
|
||||
i = me.skip_string(json, i)
|
||||
} else {
|
||||
if ch == "[" { depth = depth + 1 }
|
||||
else if ch == "]" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { i = i + 1 break }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
|
||||
}
|
||||
|
||||
// Read JSON literal (number/true/false/null) with position marker
|
||||
read_literal(json, idx) {
|
||||
local n = json.size()
|
||||
local i = idx
|
||||
loop(i < n) {
|
||||
local ch = json.substring(i, i + 1)
|
||||
if ch == "," || ch == "}" || ch == "]" { break }
|
||||
i = i + 1
|
||||
}
|
||||
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
|
||||
}
|
||||
|
||||
// Split JSON array at top-level commas (depth-aware, escape-aware)
|
||||
split_top_level(array_json) {
|
||||
local out = new ArrayBox()
|
||||
local n = array_json.size()
|
||||
local i = 1
|
||||
local start = 1
|
||||
local depth = 0
|
||||
local in_string = 0
|
||||
loop(i < n - 1) {
|
||||
local ch = array_json.substring(i, i + 1)
|
||||
if in_string == 1 {
|
||||
if ch == "\\" {
|
||||
i = i + 2
|
||||
} else {
|
||||
if ch == "\"" { in_string = 0 }
|
||||
i = i + 1
|
||||
}
|
||||
} else {
|
||||
if ch == "\"" {
|
||||
in_string = 1
|
||||
i = i + 1
|
||||
} else {
|
||||
if ch == "{" || ch == "[" { depth = depth + 1 }
|
||||
else if ch == "}" || ch == "]" { depth = depth - 1 }
|
||||
else if ch == "," && depth == 0 {
|
||||
local part = array_json.substring(start, i)
|
||||
out.push(part)
|
||||
start = i + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if start < n - 1 {
|
||||
out.push(array_json.substring(start, n - 1))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Unescape JSON string (convert \n, \t, \r, \\, \" to actual characters)
|
||||
unescape_string(s) {
|
||||
if s == null { return "" }
|
||||
local out = ""
|
||||
local i = 0
|
||||
local n = s.size()
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i + 1)
|
||||
if ch == "\\" && i + 1 < n {
|
||||
local nx = s.substring(i + 1, i + 2)
|
||||
if nx == "\"" {
|
||||
out = out + "\""
|
||||
i = i + 2
|
||||
} else {
|
||||
if nx == "\\" {
|
||||
out = out + "\\"
|
||||
i = i + 2
|
||||
} else {
|
||||
if nx == "n" {
|
||||
out = out + "\n"
|
||||
i = i + 2
|
||||
} else {
|
||||
if nx == "r" {
|
||||
out = out + "\r"
|
||||
i = i + 2
|
||||
} else {
|
||||
if nx == "t" {
|
||||
out = out + "\t"
|
||||
i = i + 2
|
||||
} else {
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
159
lang/src/shared/json/mir_builder2.hako
Normal file
159
lang/src/shared/json/mir_builder2.hako
Normal file
@ -0,0 +1,159 @@
|
||||
// mir_builder2.hako — Instance builder(引数渡しバグ回避のため、非staticで内部状態を保持)
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using lang.compiler.emit.common.json_emit as JsonEmitBox
|
||||
using lang.compiler.emit.common.mir_emit as MirEmitBox
|
||||
using lang.compiler.emit.common.header_emit as HeaderEmitBox
|
||||
|
||||
box MirJsonBuilder2 {
|
||||
st: MapBox
|
||||
|
||||
// ---- lifecycle ----
|
||||
setup() {
|
||||
me.st = map({
|
||||
buf: "",
|
||||
phase: 0,
|
||||
first_inst: 1,
|
||||
blocks: new ArrayBox(),
|
||||
cur_block_index: -1,
|
||||
fn_name: "",
|
||||
prefer_rebuild: 0,
|
||||
append_headers: 0,
|
||||
append_insts: 0
|
||||
})
|
||||
}
|
||||
|
||||
// ---- internal helpers ----
|
||||
_get_buf() { return me.st.get("buf") }
|
||||
_set_buf(s) { me.st.set("buf", s) }
|
||||
_append(s) { local b = me._get_buf() me._set_buf(b + s) }
|
||||
_append_header(s) {
|
||||
local on = me.st.get("append_headers")
|
||||
if on != null && on == 1 { me._append(s) }
|
||||
}
|
||||
_append_inst_json(node) {
|
||||
// Append per-instruction JSON only when explicitly enabled (dev aid)
|
||||
local on = me.st.get("append_insts")
|
||||
if on != null && on == 1 { me._append(JsonEmitBox.to_json(node)) }
|
||||
}
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
_cur_insts() {
|
||||
local blks = me.st.get("blocks")
|
||||
if blks == null || blks.size == null { return null }
|
||||
local n = blks.size()
|
||||
if n <= 0 { return null }
|
||||
local idx = me.st.get("cur_block_index")
|
||||
if idx == null || idx < 0 || idx >= n { idx = n - 1 }
|
||||
local blk = blks.get(idx)
|
||||
return blk.get("instructions")
|
||||
}
|
||||
_comma_if_needed() {
|
||||
// Only relevant when appending instruction JSON text
|
||||
local ai = me.st.get("append_insts")
|
||||
if ai == null || ai != 1 { return }
|
||||
local first = me.st.get("first_inst")
|
||||
if first == 1 { me.st.set("first_inst", 0) return }
|
||||
me._append(",")
|
||||
}
|
||||
|
||||
// ---- building API(インスタンスメソッド)----
|
||||
start_module() { me.st.set("phase", 1) me._append_header("{\"functions\":[") }
|
||||
start_function(name) {
|
||||
me.st.set("phase", 2)
|
||||
me.st.set("fn_name", name)
|
||||
me.st.set("blocks", new ArrayBox())
|
||||
me.st.set("cur_block_index", -1)
|
||||
me._append_header("{\"name\":" + StringHelpers.json_quote(name) + ",\"params\":[],\"blocks\":[")
|
||||
}
|
||||
start_block(id) {
|
||||
me.st.set("phase", 3)
|
||||
me.st.set("first_inst", 1)
|
||||
// 配列側
|
||||
local blk = map({ id: id, instructions: new ArrayBox() })
|
||||
local blks = me.st.get("blocks")
|
||||
blks.push(blk)
|
||||
me.st.set("cur_block_index", blks.size() - 1)
|
||||
// 文字列側
|
||||
me._append_header("{\"id\":" + me._int_to_str(id) + ",\"instructions\":[")
|
||||
}
|
||||
end_block_continue() { me._append_header("]},") }
|
||||
end_all() {
|
||||
me.st.set("phase", 5)
|
||||
// Prefer route to rebuild on to_string() for safer parity
|
||||
me.st.set("prefer_rebuild", 1)
|
||||
me._append_header("]}]}]}")
|
||||
}
|
||||
|
||||
add_const(dst, val) {
|
||||
me._comma_if_needed()
|
||||
// 構造
|
||||
local node = MirEmitBox.make_const(dst, val)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
// テキスト
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
add_compare(kind, lhs, rhs, dst) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_compare(kind, lhs, rhs, dst)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
add_copy(dst, src) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_copy(dst, src)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
add_branch(cond, then_id, else_id) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_branch(cond, then_id, else_id)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
add_jump(target) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_jump(target)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
add_ret(val) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_ret(val)
|
||||
local insts = me._cur_insts()
|
||||
if insts != null && insts.push != null { insts.push(node) }
|
||||
me._append_inst_json(node)
|
||||
}
|
||||
|
||||
// ---- outputs ----
|
||||
to_string() {
|
||||
local pref = me.st.get("prefer_rebuild")
|
||||
if pref != null && pref == 1 { return me.to_string_rebuild() }
|
||||
return me._get_buf()
|
||||
}
|
||||
to_string_rebuild() {
|
||||
// 構造(blocks → instructions)を正として再構築(HeaderEmitBox 経由)
|
||||
local name = me.st.get("fn_name")
|
||||
local blks = me.st.get("blocks")
|
||||
local blocks = new ArrayBox()
|
||||
if blks != null && blks.size != null {
|
||||
local n = blks.size()
|
||||
local i = 0
|
||||
loop (i < n) {
|
||||
local blk = blks.get(i)
|
||||
local bid = blk.get("id")
|
||||
local insts = blk.get("instructions")
|
||||
blocks.push(HeaderEmitBox.make_block(bid, insts))
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
local fns = new ArrayBox()
|
||||
fns.push(HeaderEmitBox.make_function_main(blocks))
|
||||
return JsonEmitBox.to_json(HeaderEmitBox.make_module_with_functions(fns))
|
||||
}
|
||||
}
|
||||
|
||||
static box MirJsonBuilder2Stub { main(args) { return 0 } }
|
||||
439
lang/src/shared/json/mir_builder_min.hako
Normal file
439
lang/src/shared/json/mir_builder_min.hako
Normal file
@ -0,0 +1,439 @@
|
||||
// mir_builder_min.nyash — Minimal MIR(JSON v0) builder for selfhost tests
|
||||
// Scope: selfhost only (apps/selfhost/...); no core/runtime changes.
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
|
||||
using lang.compiler.emit.common.json_emit as JsonEmitBox
|
||||
using lang.compiler.emit.common.mir_emit as MirEmitBox
|
||||
using lang.compiler.emit.common.call_emit as CallEmitBox
|
||||
using lang.compiler.emit.common.newbox_emit as NewBoxEmitBox
|
||||
using "lang/src/shared/json/json_inst_encode_box.hako" as JsonInstEncodeBox
|
||||
|
||||
box MirJsonBuilderMin {
|
||||
buf: StringBox
|
||||
phase: IntegerBox
|
||||
first_inst: IntegerBox
|
||||
blocks: ArrayBox
|
||||
cur_block_index: IntegerBox
|
||||
fn_name: StringBox
|
||||
trace: IntegerBox
|
||||
verify: IntegerBox
|
||||
|
||||
birth() {
|
||||
me.buf = ""
|
||||
me.phase = 0
|
||||
me.first_inst = 1
|
||||
me.blocks = new ArrayBox()
|
||||
me.cur_block_index = -1
|
||||
me.fn_name = ""
|
||||
me.trace = 0
|
||||
me.verify = 0
|
||||
}
|
||||
|
||||
// Internal helpers
|
||||
_get_buf() { return me.buf }
|
||||
_set_buf(s) { me.buf = s return me }
|
||||
_append(s) {
|
||||
me.buf = me.buf + s
|
||||
return me
|
||||
}
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
_to_i64(x) { return StringHelpers.to_i64(x) }
|
||||
|
||||
_quote(s) { return StringHelpers.json_quote(s) }
|
||||
|
||||
_ids_range_json(start, count) {
|
||||
local i = 0
|
||||
local out = "["
|
||||
loop (i < count) {
|
||||
if i > 0 { out = out + "," }
|
||||
out = out + me._int_to_str(start + i)
|
||||
i = i + 1
|
||||
}
|
||||
out = out + "]"
|
||||
return out
|
||||
}
|
||||
|
||||
_ids_array_from_json_text(arr_text) {
|
||||
local out = new ArrayBox()
|
||||
if arr_text == null { return out }
|
||||
local s = "" + arr_text
|
||||
local i = 0
|
||||
loop (i < s.size()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch >= "0" && ch <= "9" {
|
||||
local j = i
|
||||
loop (j < s.size()) {
|
||||
local cj = s.substring(j, j+1)
|
||||
if !(cj >= "0" && cj <= "9") { break }
|
||||
j = j + 1
|
||||
}
|
||||
local num = me._to_i64(s.substring(i, j))
|
||||
out.push(num)
|
||||
i = j
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_ensure_phase(want) {
|
||||
if me.phase + 1 < want { return me }
|
||||
return me
|
||||
}
|
||||
|
||||
// Public builder steps
|
||||
start_module() {
|
||||
if me.trace == 1 { print("[DEBUG start_module] starting") }
|
||||
me.phase = 1
|
||||
return me._append("{\"functions\":[")
|
||||
}
|
||||
|
||||
start_function(name) {
|
||||
if me.trace == 1 { print("[DEBUG start_function] name=" + name) }
|
||||
me.phase = 2
|
||||
me.fn_name = name
|
||||
me.blocks = new ArrayBox()
|
||||
me.cur_block_index = -1
|
||||
if me.trace == 1 { print("[DEBUG start_function] after set, fn_name=" + me.fn_name) }
|
||||
local b = "{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
|
||||
return me._append(b)
|
||||
}
|
||||
|
||||
start_block(id) {
|
||||
me.phase = 3
|
||||
me.first_inst = 1
|
||||
local blk = map({ id: id, instructions: new ArrayBox() })
|
||||
me.blocks.push(blk)
|
||||
me.cur_block_index = me.blocks.size() - 1
|
||||
local b = "{\"id\":" + me._int_to_str(id) + ",\"instructions\":["
|
||||
return me._append(b)
|
||||
}
|
||||
|
||||
end_block_continue() {
|
||||
return me._append("]},")
|
||||
}
|
||||
|
||||
_comma_if_needed() {
|
||||
if me.first_inst == 1 {
|
||||
me.first_inst = 0
|
||||
return me
|
||||
}
|
||||
return me._append(",")
|
||||
}
|
||||
|
||||
add_const(dst, val) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_const(dst, val)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_compare(kind, lhs, rhs, dst) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_compare(kind, lhs, rhs, dst)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_binop(kind, lhs, rhs, dst) {
|
||||
me._comma_if_needed()
|
||||
local node = { op:"binop", op_kind:kind, lhs:lhs, rhs:rhs, dst:dst }
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_ret(val) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_ret(val)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_call_ids(name, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = CallEmitBox.make_call(name, args, dst)
|
||||
node.set("args_text", args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_call_range(name, start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local args_text = me._ids_range_json(start, count)
|
||||
local node = CallEmitBox.make_call(name, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_boxcall_range(method, recv_id, args_start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(args_start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local args_text = me._ids_range_json(args_start, count)
|
||||
local node = CallEmitBox.make_boxcall(method, recv_id, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_newbox_range(box_type, args_start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(args_start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local node = NewBoxEmitBox.with_args_array(NewBoxEmitBox.make_new(box_type, args, dst), args)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_newbox_ids(box_type, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = NewBoxEmitBox.make_new(box_type, args, dst)
|
||||
node = NewBoxEmitBox.with_args_text(node, args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
_args_array_json_from_ids(ids_start, count) {
|
||||
return me._ids_range_json(ids_start, count)
|
||||
}
|
||||
|
||||
add_mir_call_global_ids(name, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = CallEmitBox.make_mir_call_global(name, args, dst)
|
||||
node.set("args_text", args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_extern_ids(name, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = CallEmitBox.make_mir_call_extern(name, args, dst)
|
||||
node.set("args_text", args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_method_ids(method, recv_id, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
|
||||
node.set("args_text", args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_constructor_ids(box_type, args_json_text, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = me._ids_array_from_json_text(args_json_text)
|
||||
local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
|
||||
node.set("args_text", args_json_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_global_range(name, start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local args_text = me._ids_range_json(start, count)
|
||||
local node = CallEmitBox.make_mir_call_global(name, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_extern_range(name, start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) { args.push(start + i) i = i + 1 }
|
||||
local args_text = me._ids_range_json(start, count)
|
||||
local node = CallEmitBox.make_mir_call_extern(name, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_method_range(method, recv_id, args_start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(args_start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local args_text = me._ids_range_json(args_start, count)
|
||||
local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_mir_call_constructor_range(box_type, args_start, count, dst) {
|
||||
me._comma_if_needed()
|
||||
local args = new ArrayBox()
|
||||
local i = 0
|
||||
loop(i < count) {
|
||||
args.push(args_start + i)
|
||||
i = i + 1
|
||||
}
|
||||
local args_text = me._ids_range_json(args_start, count)
|
||||
local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
|
||||
node.set("args_text", args_text)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_branch(cond, then_id, else_id) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_branch(cond, then_id, else_id)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_jump(target) {
|
||||
me._comma_if_needed()
|
||||
local node = MirEmitBox.make_jump(target)
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
add_copy(dst, src) {
|
||||
me._comma_if_needed()
|
||||
local node = { op:"copy", dst:dst, src:src }
|
||||
me._push_inst_map(node)
|
||||
return me._append(JsonEmitBox.to_json(node))
|
||||
}
|
||||
|
||||
end_all() {
|
||||
me.phase = 5
|
||||
return me._append("]}]}]}")
|
||||
}
|
||||
|
||||
to_string() { return me._get_buf() }
|
||||
|
||||
_cur_insts() {
|
||||
local blks = me.blocks
|
||||
if blks == null { return null }
|
||||
local n = 0
|
||||
n = BoxHelpers.array_len(blks)
|
||||
if n <= 0 { return null }
|
||||
local idx = me.cur_block_index
|
||||
if idx == null { idx = -1 }
|
||||
if idx < 0 || idx >= n { idx = n - 1 }
|
||||
local blk = BoxHelpers.array_get(blks, idx)
|
||||
if blk == null { return null }
|
||||
return BoxHelpers.map_get(blk, "instructions")
|
||||
}
|
||||
|
||||
verify_builder_state() { return 0 }
|
||||
|
||||
_verify_enabled() {
|
||||
return me.verify
|
||||
}
|
||||
|
||||
_trace_enabled() {
|
||||
return me.trace
|
||||
}
|
||||
|
||||
_push_inst(node) {
|
||||
local before = 0
|
||||
local insts0 = me._cur_insts()
|
||||
before = BoxHelpers.array_len(insts0)
|
||||
if insts0 == null || insts0.push == null { return }
|
||||
insts0.push(node)
|
||||
if me._verify_enabled() == 1 {
|
||||
local insts1 = me._cur_insts()
|
||||
local after = BoxHelpers.array_len(insts1)
|
||||
local op_str = BoxHelpers.map_get(node, "op")
|
||||
if after != before + 1 { print("[builder-verify] size mismatch before=" + before + " after=" + after + " op=" + op_str) }
|
||||
}
|
||||
if me._trace_enabled() == 1 {
|
||||
local op_str_trace = BoxHelpers.map_get(node, "op")
|
||||
print("[builder-trace] push op=" + op_str_trace)
|
||||
}
|
||||
}
|
||||
|
||||
_push_inst_map(node) { return me._push_inst(node) }
|
||||
|
||||
enable_trace(on) {
|
||||
if on == null { on = 1 }
|
||||
me.trace = on
|
||||
return me
|
||||
}
|
||||
|
||||
enable_verify(on) {
|
||||
if on == null { on = 1 }
|
||||
me.verify = on
|
||||
return me
|
||||
}
|
||||
|
||||
get_current_instructions() { return me._cur_insts() }
|
||||
|
||||
get_blocks_array() { return me.blocks }
|
||||
|
||||
get_function_structure() {
|
||||
local blks = me.get_blocks_array()
|
||||
return map({ name: me.fn_name, params: new ArrayBox(), blocks: blks })
|
||||
}
|
||||
|
||||
emit_to_string() { return me.to_string() }
|
||||
|
||||
_inst_json(node) { return JsonInstEncodeBox.encode(node) }
|
||||
|
||||
to_string_rebuild() {
|
||||
local name = me.fn_name
|
||||
local blks = me.get_blocks_array()
|
||||
local blks_size_str = "null"
|
||||
local blks_len = BoxHelpers.array_len(blks)
|
||||
if blks_len >= 0 { blks_size_str = me._int_to_str(blks_len) }
|
||||
print("[DEBUG rebuild] fn_name=" + name + " blks.size()=" + blks_size_str)
|
||||
local out = "{\"functions\":[{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
|
||||
local n = blks_len
|
||||
print("[DEBUG rebuild] n=" + me._int_to_str(n))
|
||||
local i = 0
|
||||
loop (i < n) {
|
||||
if i > 0 { out = out + "," }
|
||||
local blk = BoxHelpers.array_get(blks, i)
|
||||
local bid = BoxHelpers.map_get(blk, "id")
|
||||
out = out + "{\"id\":" + me._int_to_str(bid) + ",\"instructions\":["
|
||||
local insts = BoxHelpers.map_get(blk, "instructions")
|
||||
local m = BoxHelpers.array_len(insts)
|
||||
local j = 0
|
||||
loop (j < m) {
|
||||
if j > 0 { out = out + "," }
|
||||
out = out + me._inst_json(BoxHelpers.array_get(insts, j))
|
||||
j = j + 1
|
||||
}
|
||||
out = out + "]}"
|
||||
i = i + 1
|
||||
}
|
||||
out = out + "]}]}"
|
||||
return out
|
||||
}
|
||||
}
|
||||
119
lang/src/shared/json/mir_v1_adapter.hako
Normal file
119
lang/src/shared/json/mir_v1_adapter.hako
Normal file
@ -0,0 +1,119 @@
|
||||
// mir_v1_adapter.nyash — Minimal JSON v1 (mir_call) to v0 adapter for selfhost
|
||||
// Scope: selfhost only. Transforms op:"mir_call" into legacy v0 ops (call/boxcall/newbox).
|
||||
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box MirJsonV1Adapter {
|
||||
// Delegate to JsonCursorBox (escape-aware implementations, fixes 2 escape bugs)
|
||||
_index_of_from(hay, needle, pos) {
|
||||
return JsonCursorBox.index_of_from(hay, needle, pos)
|
||||
}
|
||||
_seek_obj_end(text, obj_start) {
|
||||
return JsonCursorBox.seek_obj_end(text, obj_start)
|
||||
}
|
||||
_seek_array_end(text, pos_after_bracket) {
|
||||
// JsonCursorBox.seek_array_end expects pos at '[', adjust since we receive pos after '['
|
||||
if text == null { return -1 }
|
||||
if pos_after_bracket <= 0 { return -1 }
|
||||
local bracket_pos = pos_after_bracket - 1
|
||||
return JsonCursorBox.seek_array_end(text, bracket_pos)
|
||||
}
|
||||
_read_digits(text, pos) {
|
||||
return JsonCursorBox.digits_from(text, pos)
|
||||
}
|
||||
_read_string(text, pos_after_quote) {
|
||||
local end = me._index_of_from(text, "\"", pos_after_quote)
|
||||
if end < 0 { return "" }
|
||||
return text.substring(pos_after_quote, end)
|
||||
}
|
||||
|
||||
// Convert one mir_call object JSON segment to v0 legacy op JSON
|
||||
_convert_mir_call(seg) {
|
||||
// dst
|
||||
local dpos = me._index_of_from(seg, "\"dst\":", 0)
|
||||
local dst = 0
|
||||
if dpos >= 0 {
|
||||
local dd = me._read_digits(seg, dpos + 6)
|
||||
if dd != "" { dst = 0 + ("" + dd).size() // placeholder to avoid lints
|
||||
dst = 0 // reassign after parse
|
||||
// parse int
|
||||
local acc = 0
|
||||
local i = 0
|
||||
loop(i < dd.size()) { acc = acc * 10 + ("0123456789".indexOf(dd.substring(i,i+1))) i = i + 1 }
|
||||
dst = acc
|
||||
}
|
||||
}
|
||||
// args array substring (reuse existing as-is)
|
||||
local ak = me._index_of_from(seg, "\"args\":[", 0)
|
||||
local args_json = "[]"
|
||||
if ak >= 0 {
|
||||
local lb = me._index_of_from(seg, "[", ak)
|
||||
if lb >= 0 {
|
||||
local rb = me._seek_array_end(seg, lb + 1)
|
||||
if rb > lb { args_json = seg.substring(lb, rb + 1) }
|
||||
}
|
||||
}
|
||||
// callee type
|
||||
local ck = me._index_of_from(seg, "\"callee\":{\"type\":\"", 0)
|
||||
if ck < 0 { return seg }
|
||||
local ct = me._read_string(seg, ck + 18)
|
||||
if ct == "Global" {
|
||||
local nk = me._index_of_from(seg, "\"name\":\"", ck)
|
||||
local name = ""
|
||||
if nk >= 0 { name = me._read_string(seg, nk + 8) }
|
||||
return "{\"op\":\"call\",\"name\":\"" + name + "\",\"args\":" + args_json + ",\"dst\":" + dst + "}"
|
||||
}
|
||||
if ct == "Method" {
|
||||
local mk = me._index_of_from(seg, "\"method\":\"", ck)
|
||||
local method = ""
|
||||
if mk >= 0 { method = me._read_string(seg, mk + 10) }
|
||||
local rk = me._index_of_from(seg, "\"receiver\":", ck)
|
||||
local recv = 0
|
||||
if rk >= 0 {
|
||||
local rd = me._read_digits(seg, rk + 11)
|
||||
if rd != "" {
|
||||
local acc2 = 0
|
||||
local i2 = 0
|
||||
loop(i2 < rd.size()) { acc2 = acc2 * 10 + ("0123456789".indexOf(rd.substring(i2,i2+1))) i2 = i2 + 1 }
|
||||
recv = acc2
|
||||
}
|
||||
}
|
||||
return "{\"op\":\"boxcall\",\"method\":\"" + method + "\",\"recv\":" + recv + ",\"args\":" + args_json + ",\"dst\":" + dst + "}"
|
||||
}
|
||||
if ct == "Constructor" {
|
||||
local tk = me._index_of_from(seg, "\"box_type\":\"", ck)
|
||||
local tname = ""
|
||||
if tk >= 0 { tname = me._read_string(seg, tk + 12) }
|
||||
return "{\"op\":\"newbox\",\"box_type\":\"" + tname + "\",\"args\":" + args_json + ",\"dst\":" + dst + "}"
|
||||
}
|
||||
// default: return original segment
|
||||
return seg
|
||||
}
|
||||
|
||||
// Public API: convert all mir_call objects within MIR(JSON v1) to v0 legacy ops
|
||||
to_v0(mjson) {
|
||||
if mjson == null { return null }
|
||||
local out = mjson
|
||||
local cur = 0
|
||||
loop(true) {
|
||||
local op = me._index_of_from(out, "\"op\":\"mir_call\"", cur)
|
||||
if op < 0 { break }
|
||||
// find object bounds
|
||||
local start = op
|
||||
loop(start >= 0) {
|
||||
if out.substring(start, start+1) == "{" { break }
|
||||
start = start - 1
|
||||
}
|
||||
if start < 0 { break }
|
||||
local end = me._seek_obj_end(out, start)
|
||||
if end <= start { break }
|
||||
local seg = out.substring(start, end)
|
||||
local rep = me._convert_mir_call(seg)
|
||||
out = out.substring(0, start) + rep + out.substring(end, out.size())
|
||||
cur = start + rep.size()
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
static box MirJsonV1AdapterStub { main(args) { return 0 } }
|
||||
26
lang/src/shared/json/mir_v1_meta_inject_box.hako
Normal file
26
lang/src/shared/json/mir_v1_meta_inject_box.hako
Normal file
@ -0,0 +1,26 @@
|
||||
// mir_v1_meta_inject_box.hako — Inject metadata.extern_c and v1 header into MIR(JSON)
|
||||
// Responsibility: Convert a minimal v0-like MIR JSON (with only {"functions":[...]})
|
||||
// into a v1 root with kind/schema_version/metadata.extern_c while preserving functions.
|
||||
|
||||
static box MirV1MetaInjectBox {
|
||||
// Injects: {"kind":"MIR","schema_version":"1.0","metadata":{"extern_c":EXTERNS}, ...}
|
||||
// - mir_json must be an object JSON starting with '{' and ending with '}'
|
||||
// - externs_json must be a JSON array text (e.g., "[]" or "[{\"func\":...,\"symbol\":...}]")
|
||||
inject_meta_externs(mir_json, externs_json) {
|
||||
if mir_json == null { return null }
|
||||
local s = "" + mir_json
|
||||
if s.size() < 2 { return s }
|
||||
if s.substring(0,1) != "{" { return s }
|
||||
local ext = externs_json
|
||||
if ext == null { ext = "[]" }
|
||||
if ext.size() == 0 { ext = "[]" }
|
||||
// Construct v1 root by inserting header + metadata at the beginning
|
||||
// { <insert here> existing_without_leading_brace
|
||||
local tail = s.substring(1, s.size())
|
||||
local head = "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"metadata\":{\"extern_c\":" + ext + "},"
|
||||
return head + tail
|
||||
}
|
||||
}
|
||||
|
||||
static box MirV1MetaInjectMain { main(args) { return 0 } }
|
||||
|
||||
74
lang/src/shared/json/utils/json_frag.hako
Normal file
74
lang/src/shared/json/utils/json_frag.hako
Normal file
@ -0,0 +1,74 @@
|
||||
// json_frag.hako — JSON v0 断片抽出ユーティリティ(Box)
|
||||
// 責務: 文字列JSONから key:int / key:str を簡便に取り出す。
|
||||
// 非責務: 実行・評価(構造検査やVM実行は他箱に委譲)。
|
||||
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box JsonFragBox {
|
||||
// 基本ヘルパ
|
||||
index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
|
||||
read_digits(text, pos) { return StringHelpers.read_digits(text, pos) }
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
|
||||
// key に続く数値(最初の一致)を返す。見つからなければ null。
|
||||
get_int(seg, key) {
|
||||
local pat1 = "\"" + key + "\":"
|
||||
local p = me.index_of_from(seg, pat1, 0)
|
||||
if p >= 0 {
|
||||
local v = me.read_digits(seg, p + pat1.size())
|
||||
if v != "" { return me._str_to_int(v) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// key に続く "..." の文字列(最初の一致)を返す。見つからなければ空文字。
|
||||
get_str(seg, key) {
|
||||
local pat = "\"" + key + "\":\""
|
||||
local p = me.index_of_from(seg, pat, 0)
|
||||
if p >= 0 {
|
||||
local vstart = p + pat.size() // start of value (right after opening quote)
|
||||
local vend = JsonCursorBox.scan_string_end(seg, vstart - 1)
|
||||
if vend > vstart { return seg.substring(vstart, vend) }
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Strict variants: emit an error when the key is missing
|
||||
get_int_strict(seg, key) {
|
||||
local v = me.get_int(seg, key)
|
||||
if v == null {
|
||||
print("[ERROR] Missing key: " + key)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
get_str_strict(seg, key) {
|
||||
local v = me.get_str(seg, key)
|
||||
if v == "" {
|
||||
print("[ERROR] Missing key: " + key)
|
||||
}
|
||||
return v
|
||||
}
|
||||
// ブロック0の instructions を丸ごと返す(配列の中身のみ返す)。
|
||||
block0_segment(mjson) {
|
||||
if mjson == null { return "" }
|
||||
// Find the instructions array start reliably
|
||||
local key = "\"instructions\":["
|
||||
local pk = call("String.indexOf/2", mjson, key)
|
||||
if pk < 0 { return "" }
|
||||
// '[' position
|
||||
local arr_bracket = pk + key.size() - 1
|
||||
// Use escape-aware scanner to find matching ']'
|
||||
local endp = JsonCursorBox.seek_array_end(mjson, arr_bracket)
|
||||
if endp < 0 { return "" }
|
||||
return mjson.substring(arr_bracket + 1, endp)
|
||||
}
|
||||
|
||||
|
||||
// Alias for legacy/buggy resolvers that drop underscores in method names.
|
||||
// Keep as a thin forwarder to preserve strict naming in source while
|
||||
// unblocking runtimes that accidentally call `block0segment`.
|
||||
}
|
||||
18
lang/src/shared/json_adapter.hako
Normal file
18
lang/src/shared/json_adapter.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// Adapter for JSON cursor operations (extracted)
|
||||
// Wraps MiniJsonCur and exposes a stable facade
|
||||
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonCur
|
||||
|
||||
static box MiniJson {
|
||||
read_quoted_from(s, pos) {
|
||||
local cur = new MiniJsonCur()
|
||||
return cur.read_quoted_from(s, pos)
|
||||
}
|
||||
read_digits_from(s, pos) {
|
||||
local cur = new MiniJsonCur()
|
||||
return cur.read_digits_from(s, pos)
|
||||
}
|
||||
next_non_ws(s, pos) {
|
||||
local cur = new MiniJsonCur()
|
||||
return cur.next_non_ws(s, pos)
|
||||
}
|
||||
}
|
||||
21
lang/src/shared/mir/README.md
Normal file
21
lang/src/shared/mir/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# selfhost/shared/mir — JSON emit / LoopForm helpers
|
||||
|
||||
責務
|
||||
- MIR(JSON v0) の構築・出力(Gate C 互換)。
|
||||
- ループ構造(LoopForm: Header/Latch/Exit)の最小組み立て。
|
||||
|
||||
方針(型依存ロジックの統一)
|
||||
- Array の長さ: `ArrayBox.size/1` → `StringHelpers.to_i64` で整数化。
|
||||
- Map の取得: `MapBox.get/2`(`type`/`value` 等)でアクセス。
|
||||
- 整数化: `StringHelpers.to_i64` を使用。文字列ヒューリスティックは禁止。
|
||||
- JSON 文字列化: `StringHelpers.json_quote` を使用(安全なエスケープ)。
|
||||
|
||||
禁止(Fail-Fast 防止のため)
|
||||
- `.getField` 相当のフィールド直参照の混入。
|
||||
- 文字列化結果からの数値抽出(`"MapBox("` 判定など)。
|
||||
|
||||
関連
|
||||
- `json_emit_box.hako`: Gate C JSON 出力(numbers unwrapped)。
|
||||
- `mir_schema_box.hako`: MIR(JSON) 構築ヘルパー(v0 スキーマ)。
|
||||
- `loop_form_box.hako`: LoopForm 構造の最小組み立て。
|
||||
|
||||
409
lang/src/shared/mir/block_builder_box.hako
Normal file
409
lang/src/shared/mir/block_builder_box.hako
Normal file
@ -0,0 +1,409 @@
|
||||
// selfhost/shared/mir/block_builder_box.hako
|
||||
// BlockBuilderBox — small helpers to assemble P1 shapes
|
||||
|
||||
using "lang/src/shared/mir/mir_schema_box.hako" as MirSchemaBox
|
||||
using "lang/src/shared/mir/loop_form_box.hako" as LoopFormBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box BlockBuilderBox {
|
||||
|
||||
_array_len(arr) {
|
||||
if arr == null { return 0 }
|
||||
return StringHelpers.to_i64(call("ArrayBox.size/1", arr))
|
||||
}
|
||||
|
||||
_unwrap_vid(val) {
|
||||
if val == null { return null }
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local inner = call("MapBox.get/2", val, "value")
|
||||
if inner != null { return StringHelpers.to_i64(inner) }
|
||||
}
|
||||
return StringHelpers.to_i64(val)
|
||||
}
|
||||
|
||||
// Ensure every block ends with a terminator (ret/jump/branch/throw)
|
||||
_ensure_terminators(mod_full) {
|
||||
if mod_full == null { return mod_full }
|
||||
local fns = call("MapBox.get/2", mod_full, "functions")
|
||||
if fns == null { return mod_full }
|
||||
local fn_count = me._array_len(fns)
|
||||
local fi = 0
|
||||
loop(fi < fn_count) {
|
||||
local func = call("ArrayBox.get/2", fns, fi)
|
||||
if func != null {
|
||||
local blocks = call("MapBox.get/2", func, "blocks")
|
||||
if blocks != null {
|
||||
local bn = me._array_len(blocks)
|
||||
local bi = 0
|
||||
loop(bi < bn) {
|
||||
local block = call("ArrayBox.get/2", blocks, bi)
|
||||
local insts = null
|
||||
if block != null { insts = call("MapBox.get/2", block, "instructions") }
|
||||
if insts != null {
|
||||
local n = me._array_len(insts)
|
||||
if n > 0 {
|
||||
local last = call("ArrayBox.get/2", insts, n - 1)
|
||||
local op = ""
|
||||
if last != null {
|
||||
local maybe_op = call("MapBox.get/2", last, "op")
|
||||
if maybe_op != null { op = "" + maybe_op }
|
||||
}
|
||||
if !(op == "ret" || op == "return" || op == "jump" || op == "branch" || op == "throw") {
|
||||
local idx = 0
|
||||
local max_vid = -1
|
||||
local last_dst = -1
|
||||
loop(idx < n) {
|
||||
local inst = call("ArrayBox.get/2", insts, idx)
|
||||
local dst_map = null
|
||||
if inst != null { dst_map = call("MapBox.get/2", inst, "dst") }
|
||||
local vid = me._unwrap_vid(dst_map)
|
||||
if vid != null {
|
||||
if vid > max_vid { max_vid = vid }
|
||||
last_dst = vid
|
||||
}
|
||||
idx = idx + 1
|
||||
}
|
||||
if last_dst < 0 {
|
||||
last_dst = max_vid + 1
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(last_dst, 0))
|
||||
}
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(last_dst))
|
||||
}
|
||||
} else {
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(0, 0))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(0))
|
||||
}
|
||||
}
|
||||
bi = bi + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
return mod_full
|
||||
}
|
||||
// const→ret (returns literal)
|
||||
const_ret(val) {
|
||||
local insts = new ArrayBox()
|
||||
// Public name route: MirSchemaBox.* helpers
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(1, val))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(1))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
|
||||
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(module)
|
||||
}
|
||||
|
||||
_ops_str_from_insts(insts) {
|
||||
if insts == null { return "" }
|
||||
local i = 0; local out = ""
|
||||
local count = me._array_len(insts)
|
||||
loop(i < count) {
|
||||
if i > 0 { out = out + "," }
|
||||
local it = call("ArrayBox.get/2", insts, i)
|
||||
if it != null {
|
||||
local op = call("MapBox.get/2", it, "op")
|
||||
if op != null { out = out + op } else { out = out + "?" }
|
||||
} else { out = out + "?" }
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_ops_str_from_blocks(blocks) {
|
||||
if blocks == null { return "" }
|
||||
local i = 0; local all = ""
|
||||
local count = me._array_len(blocks)
|
||||
loop(i < count) {
|
||||
if i > 0 { all = all + "|" }
|
||||
local b = call("ArrayBox.get/2", blocks, i)
|
||||
local insts = null
|
||||
if b != null { insts = call("MapBox.get/2", b, "instructions") }
|
||||
all = all + me._ops_str_from_insts(insts)
|
||||
i = i + 1
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
const_ret_ops(val) {
|
||||
// Op-shape helper (no MIR dependency): const → ret
|
||||
// Returns a stable summary string for lightweight tests
|
||||
return "const,ret"
|
||||
}
|
||||
|
||||
// compare→branch→jump→ret (diamond)
|
||||
compare_branch(lhs, rhs, cmp) {
|
||||
// entry
|
||||
local b0 = new ArrayBox()
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, lhs))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, rhs))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_compare(cmp, 1, 2, 3))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_branch(3, 1, 2))
|
||||
// then/else
|
||||
local b1 = new ArrayBox(); call("ArrayBox.push/2", b1, MirSchemaBox.inst_const(6, 1)); call("ArrayBox.push/2", b1, MirSchemaBox.inst_jump(3))
|
||||
local b2 = new ArrayBox(); call("ArrayBox.push/2", b2, MirSchemaBox.inst_const(6, 0)); call("ArrayBox.push/2", b2, MirSchemaBox.inst_jump(3))
|
||||
// merge
|
||||
local b3 = new ArrayBox(); call("ArrayBox.push/2", b3, MirSchemaBox.inst_ret(6))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0))
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(1, b1))
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(2, b2))
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(3, b3))
|
||||
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(module)
|
||||
}
|
||||
|
||||
// binop: const lhs/rhs; binop -> dst=3; ret 3
|
||||
binop(lhs, rhs, opk) {
|
||||
local b0 = new ArrayBox()
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, lhs))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, rhs))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_binop(opk, 1, 2, 3))
|
||||
call("ArrayBox.push/2", b0, MirSchemaBox.inst_ret(3))
|
||||
local blocks = new ArrayBox(); call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0))
|
||||
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(module)
|
||||
}
|
||||
|
||||
binop_ops(lhs, rhs, opk) {
|
||||
// Op-shape helper (no MIR dependency): const,const → binop → ret
|
||||
// Returns a stable summary string for lightweight tests
|
||||
return "const,const,binop,ret"
|
||||
}
|
||||
|
||||
// loop_counter: ops summary only(配列/Map生成を伴わない純テスト用)
|
||||
loop_counter_ops(limit) {
|
||||
// Summary of operations by shape (preheader|header|body paths|latch|exit)
|
||||
// P1 intent: const,const,const,const,const,const,jump|compare,branch|binop,jump|phi,phi,jump|phi,ret
|
||||
return "const,const,const,const,const,const,jump|compare,branch|binop,jump|phi,phi,jump|phi,ret"
|
||||
}
|
||||
|
||||
// loop_counter: while (i < limit) { i = i + 1 } ; return i
|
||||
loop_counter(limit) {
|
||||
local b0 = new ArrayBox(); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, 0)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, limit)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(4, 1)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_jump(1))
|
||||
local b1 = new ArrayBox(); call("ArrayBox.push/2", b1, MirSchemaBox.inst_compare("Lt", 1, 2, 3)); call("ArrayBox.push/2", b1, MirSchemaBox.inst_branch(3, 2, 3))
|
||||
local b2 = new ArrayBox(); call("ArrayBox.push/2", b2, MirSchemaBox.inst_binop("Add", 1, 4, 1)); call("ArrayBox.push/2", b2, MirSchemaBox.inst_jump(1))
|
||||
local b3 = new ArrayBox(); call("ArrayBox.push/2", b3, MirSchemaBox.inst_ret(1))
|
||||
local blocks = new ArrayBox();
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(1, b1)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(2, b2)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(3, b3))
|
||||
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(module)
|
||||
}
|
||||
|
||||
// LoopForm variant (with PHI + structured exit) — used by LoopForm tests/bring-up
|
||||
loop_counter_loopform(limit, skip_value, break_value) {
|
||||
local module = LoopFormBox.loop_counter(limit, skip_value, break_value)
|
||||
return me._ensure_terminators(module)
|
||||
}
|
||||
|
||||
loop_counter_loopform_json(limit, skip_value, break_value) {
|
||||
local module = me.loop_counter_loopform(limit, skip_value, break_value)
|
||||
return JsonEmitBox.to_json(module)
|
||||
}
|
||||
|
||||
to_gate_c_json(module) {
|
||||
if module == null { return "" }
|
||||
// NOTE: Caller (e.g. extern_call_ival_ret) already calls _ensure_terminators
|
||||
// Double-call causes corruption. Skip normalization here.
|
||||
return JsonEmitBox.to_json(module)
|
||||
}
|
||||
|
||||
compare_branch_ops(lhs, rhs, cmp) {
|
||||
// Op-shape helper (no MIR dependency):
|
||||
// b0: const,const,compare,branch | b1: const,jump | b2: const,jump | b3: ret
|
||||
return "const,const,compare,branch|const,jump|const,jump|ret"
|
||||
}
|
||||
|
||||
// --- P3 helpers: minimal mir_call shapes ---
|
||||
// extern call (e.g., nyrt.ops.op_eq) with provided arg ids and return
|
||||
extern_call_ret(name, args_ids, dst) {
|
||||
local b0 = [ MirSchemaBox.inst_mir_call_extern(name, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
global_call_ret(name, args_ids, dst, name_literal) {
|
||||
local actual_name = name
|
||||
if name_literal != null {
|
||||
local repr = "" + name
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_name = name_literal
|
||||
}
|
||||
}
|
||||
local b0 = [ MirSchemaBox.inst_mir_call_global(actual_name, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
method_call_ret(method, recv_id, args_ids, dst, method_literal) {
|
||||
local actual_method = method
|
||||
if method_literal != null {
|
||||
local repr = "" + method
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_method = method_literal
|
||||
}
|
||||
}
|
||||
local b0 = [ MirSchemaBox.inst_mir_call_method(actual_method, recv_id, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
constructor_call_ret(box_type, args_ids, dst, box_type_literal) {
|
||||
local actual_box_type = box_type
|
||||
if box_type_literal != null {
|
||||
local repr = "" + box_type
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_box_type = box_type_literal
|
||||
}
|
||||
}
|
||||
local b0 = [ MirSchemaBox.inst_mir_call_constructor(actual_box_type, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
// --- P4 thin adapters: immediate-value variants -------------------------
|
||||
// These helpers materialize integer constants for arguments/receiver and
|
||||
// then reuse the minimal call+ret shapes above. Output remains
|
||||
// { version, kind, functions: [...] } compatible.
|
||||
|
||||
extern_call_ival_ret(name, arg_vals, name_literal) {
|
||||
// Materialize r1..rN from arg_vals, call -> r(N+1), ret r(N+1)
|
||||
// Pattern: exact copy of const_ret structure (new ArrayBox(), direct inline)
|
||||
local insts = new ArrayBox()
|
||||
// Preserve literal name even if caller-side evaluation overwrote the first argument.
|
||||
local actual_name = name
|
||||
if name_literal != null {
|
||||
local name_repr = "" + name
|
||||
if name_repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_name = name_literal
|
||||
}
|
||||
}
|
||||
local n = 0
|
||||
local i = 0
|
||||
local m = 0
|
||||
if arg_vals != null { m = me._array_len(arg_vals) }
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
|
||||
i = i + 1
|
||||
}
|
||||
n = m
|
||||
local args_ids = new ArrayBox()
|
||||
i = 0
|
||||
loop(i < n) {
|
||||
call("ArrayBox.push/2", args_ids, i)
|
||||
i = i + 1
|
||||
}
|
||||
local dst = n
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_extern(actual_name, args_ids, dst))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
global_call_ival_ret(name, arg_vals, name_literal) {
|
||||
local insts = new ArrayBox()
|
||||
local actual_name = name
|
||||
if name_literal != null {
|
||||
local repr = "" + name
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_name = name_literal
|
||||
}
|
||||
}
|
||||
local m = 0
|
||||
if arg_vals != null { m = me._array_len(arg_vals) }
|
||||
local i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
|
||||
i = i + 1
|
||||
}
|
||||
local args_ids = new ArrayBox()
|
||||
i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", args_ids, i)
|
||||
i = i + 1
|
||||
}
|
||||
local dst = m
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_global(actual_name, args_ids, dst))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
method_call_ival_ret(method, recv_val, arg_vals, method_literal) {
|
||||
local insts = new ArrayBox()
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(0, recv_val))
|
||||
local m = 0
|
||||
if arg_vals != null { m = me._array_len(arg_vals) }
|
||||
local i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(1 + i, call("ArrayBox.get/2", arg_vals, i)))
|
||||
i = i + 1
|
||||
}
|
||||
local args_ids = new ArrayBox()
|
||||
i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", args_ids, 1 + i)
|
||||
i = i + 1
|
||||
}
|
||||
local dst = 1 + m
|
||||
local actual_method = method
|
||||
if method_literal != null {
|
||||
local repr = "" + method
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_method = method_literal
|
||||
}
|
||||
}
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_method(actual_method, 0, args_ids, dst))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
constructor_call_ival_ret(box_type, arg_vals, box_type_literal) {
|
||||
local insts = new ArrayBox()
|
||||
local m = 0
|
||||
if arg_vals != null { m = me._array_len(arg_vals) }
|
||||
local i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
|
||||
i = i + 1
|
||||
}
|
||||
local args_ids = new ArrayBox()
|
||||
i = 0
|
||||
loop(i < m) {
|
||||
call("ArrayBox.push/2", args_ids, i)
|
||||
i = i + 1
|
||||
}
|
||||
local dst = m
|
||||
local actual_box_type = box_type
|
||||
if box_type_literal != null {
|
||||
local repr = "" + box_type
|
||||
if repr.indexOf("ArrayBox(") == 0 {
|
||||
actual_box_type = box_type_literal
|
||||
}
|
||||
}
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_constructor(actual_box_type, args_ids, dst))
|
||||
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
|
||||
local blocks = new ArrayBox()
|
||||
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
|
||||
// ctor(ArrayBox) → method(size) → ret
|
||||
ctor_then_size_ret() {
|
||||
local b0 = [
|
||||
MirSchemaBox.inst_mir_call_constructor("ArrayBox", new ArrayBox(), 1),
|
||||
MirSchemaBox.inst_mir_call_method("size", 1, new ArrayBox(), 2),
|
||||
MirSchemaBox.inst_ret(2)
|
||||
]
|
||||
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
|
||||
return me._ensure_terminators(_m)
|
||||
}
|
||||
|
||||
}
|
||||
290
lang/src/shared/mir/json_emit_box.hako
Normal file
290
lang/src/shared/mir/json_emit_box.hako
Normal file
@ -0,0 +1,290 @@
|
||||
// selfhost/shared/mir/json_emit_box.hako
|
||||
// JsonEmitBox — Gate C JSON emitter (schema_version 1.0, numbers unwrapped)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
|
||||
|
||||
static box JsonEmitBox {
|
||||
_expect_map(val, context) {
|
||||
if BoxHelpers.is_map(val) == 1 { return val }
|
||||
print("[JsonEmitBox] dev assert failed: expected MapBox for " + context)
|
||||
call("MapBox.get/2", val, "__json_emit_expect_map")
|
||||
return val
|
||||
}
|
||||
_expect_array(val, context) {
|
||||
if BoxHelpers.is_array(val) == 1 { return val }
|
||||
print("[JsonEmitBox] dev assert failed: expected ArrayBox for " + context)
|
||||
call("ArrayBox.get/2", val, 0)
|
||||
return val
|
||||
}
|
||||
_expect_i64(val, context) {
|
||||
if val == null {
|
||||
print("[JsonEmitBox] dev assert failed: expected i64 (non-null) for " + context)
|
||||
call("MapBox.get/2", val, "__json_emit_expect_i64_null")
|
||||
return 0
|
||||
}
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local inner = call("MapBox.get/2", val, "value")
|
||||
if inner != null { return inner }
|
||||
print("[JsonEmitBox] dev assert failed: missing value in " + context)
|
||||
call("MapBox.get/2", val, "__json_emit_expect_i64_value")
|
||||
return 0
|
||||
}
|
||||
# Assume numeric immediate; avoid module function coercion for safety
|
||||
return val
|
||||
}
|
||||
|
||||
// ---- helpers ------------------------------------------------------------
|
||||
|
||||
_int_str(val) {
|
||||
return StringHelpers.int_to_str(me._expect_i64(val, "integer field"))
|
||||
}
|
||||
|
||||
_emit_vid_or_null(val) {
|
||||
if val == null { return "null" }
|
||||
return me._int_str(val)
|
||||
}
|
||||
|
||||
_emit_vid_array(arr) {
|
||||
if arr == null { return "[]" }
|
||||
local n = BoxHelpers.array_len(arr)
|
||||
return "[" + me._emit_vid_array_rec(arr, 0, n) + "]"
|
||||
}
|
||||
_emit_vid_array_rec(arr, idx, len) {
|
||||
if idx >= len { return "" }
|
||||
local head = me._emit_vid_or_null(call("ArrayBox.get/2", arr, idx))
|
||||
local tail = me._emit_vid_array_rec(arr, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
|
||||
_emit_effects(effects) {
|
||||
if effects == null { return "[]" }
|
||||
local n = BoxHelpers.array_len(effects)
|
||||
return "[" + me._emit_effects_rec(effects, 0, n) + "]"
|
||||
}
|
||||
_emit_effects_rec(effects, idx, len) {
|
||||
if idx >= len { return "" }
|
||||
local head = me._quote(call("ArrayBox.get/2", effects, idx))
|
||||
local tail = me._emit_effects_rec(effects, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
|
||||
_emit_callee(callee) {
|
||||
if callee == null { return "{\"type\":\"Extern\",\"name\":\"\"}" }
|
||||
callee = me._expect_map(callee, "mir_call.callee")
|
||||
local ty_box = call("MapBox.get/2", callee, "type")
|
||||
local ty = ""
|
||||
if ty_box != null { ty = "" + ty_box }
|
||||
if ty == "Extern" {
|
||||
local name = call("MapBox.get/2", callee, "name")
|
||||
if name == null { name = "" }
|
||||
return "{\"type\":\"Extern\",\"name\":" + me._quote(name) + "}"
|
||||
}
|
||||
if ty == "ModuleFunction" {
|
||||
local name = call("MapBox.get/2", callee, "name")
|
||||
if name == null { name = "" }
|
||||
return "{\"type\":\"ModuleFunction\",\"name\":" + me._quote(name) + "}"
|
||||
}
|
||||
if ty == "Method" {
|
||||
local box_name = call("MapBox.get/2", callee, "box_name")
|
||||
if box_name == null { box_name = "" }
|
||||
local method = call("MapBox.get/2", callee, "method")
|
||||
if method == null { method = "" }
|
||||
local receiver = call("MapBox.get/2", callee, "receiver")
|
||||
return "{\"type\":\"Method\",\"box_name\":" + me._quote(box_name) +
|
||||
",\"method\":" + me._quote(method) + ",\"receiver\":" + me._emit_vid_or_null(receiver) + "}"
|
||||
}
|
||||
if ty == "Constructor" {
|
||||
local box_type = call("MapBox.get/2", callee, "box_type")
|
||||
if box_type == null { box_type = "" }
|
||||
return "{\"type\":\"Constructor\",\"box_type\":" + me._quote(box_type) + "}"
|
||||
}
|
||||
return "{\"type\":" + me._quote(ty) + "}"
|
||||
}
|
||||
|
||||
_emit_box_value(val) {
|
||||
if val == null { return "{\"type\":\"i64\",\"value\":0}" }
|
||||
local ty = "i64"
|
||||
local inner = val
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local ty_box = call("MapBox.get/2", val, "type")
|
||||
if ty_box != null { ty = "" + ty_box }
|
||||
local inner_box = call("MapBox.get/2", val, "value")
|
||||
if inner_box != null { inner = inner_box }
|
||||
}
|
||||
return "{\"type\":" + me._quote(ty) + ",\"value\":" + me._int_str(inner) + "}"
|
||||
}
|
||||
|
||||
_quote(s) {
|
||||
if s == null { return "\"\"" }
|
||||
return StringHelpers.json_quote("" + s)
|
||||
}
|
||||
|
||||
_emit_phi_values(values) {
|
||||
if values == null { return "[]" }
|
||||
local n = BoxHelpers.array_len(values)
|
||||
return "[" + me._emit_phi_rec(values, 0, n) + "]"
|
||||
}
|
||||
_emit_phi_rec(values, idx, len) {
|
||||
if idx >= len { return "" }
|
||||
local item = call("ArrayBox.get/2", values, idx)
|
||||
local block_id = null
|
||||
local value_id = null
|
||||
if item != null {
|
||||
block_id = call("MapBox.get/2", item, "block")
|
||||
value_id = call("MapBox.get/2", item, "value")
|
||||
}
|
||||
local head = "{\"block\":" + me._int_str(block_id) + ",\"value\":" + me._int_str(value_id) + "}"
|
||||
local tail = me._emit_phi_rec(values, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
|
||||
_emit_inst(inst) {
|
||||
if inst == null { return "{}" }
|
||||
inst = me._expect_map(inst, "instruction")
|
||||
local op = call("MapBox.get/2", inst, "op")
|
||||
if op == null { op = "" }
|
||||
if op == "const" {
|
||||
return "{\"op\":\"const\",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) +
|
||||
",\"value\":" + me._emit_box_value(call("MapBox.get/2", inst, "value")) + "}"
|
||||
}
|
||||
if op == "ret" || op == "return" {
|
||||
return "{\"op\":\"ret\",\"value\":" + me._int_str(call("MapBox.get/2", inst, "value")) + "}"
|
||||
}
|
||||
if op == "binop" {
|
||||
local kind = call("MapBox.get/2", inst, "op_kind")
|
||||
if kind == null { kind = "" }
|
||||
return "{\"op\":\"binop\",\"op_kind\":" + me._quote(kind) + ",\"lhs\":" +
|
||||
me._int_str(call("MapBox.get/2", inst, "lhs")) + ",\"rhs\":" + me._int_str(call("MapBox.get/2", inst, "rhs")) +
|
||||
",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) + "}"
|
||||
}
|
||||
if op == "compare" {
|
||||
local cmp = call("MapBox.get/2", inst, "cmp")
|
||||
if cmp == null { cmp = "" }
|
||||
return "{\"op\":\"compare\",\"cmp\":" + me._quote(cmp) + ",\"lhs\":" +
|
||||
me._int_str(call("MapBox.get/2", inst, "lhs")) + ",\"rhs\":" + me._int_str(call("MapBox.get/2", inst, "rhs")) +
|
||||
",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) + "}"
|
||||
}
|
||||
if op == "branch" {
|
||||
return "{\"op\":\"branch\",\"cond\":" + me._int_str(call("MapBox.get/2", inst, "cond")) +
|
||||
",\"then\":" + me._int_str(call("MapBox.get/2", inst, "then")) +
|
||||
",\"else\":" + me._int_str(call("MapBox.get/2", inst, "else")) + "}"
|
||||
}
|
||||
if op == "jump" {
|
||||
return "{\"op\":\"jump\",\"target\":" + me._int_str(call("MapBox.get/2", inst, "target")) + "}"
|
||||
}
|
||||
if op == "phi" {
|
||||
return "{\"op\":\"phi\",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) +
|
||||
",\"values\":" + me._emit_phi_values(call("MapBox.get/2", inst, "values")) + "}"
|
||||
}
|
||||
if op == "mir_call" {
|
||||
// SSOT: payload("mir_call") のみを受理。トップレベルの互換フィールドは読まない。
|
||||
local payload = call("MapBox.get/2", inst, "mir_call")
|
||||
if payload == null { return "{\"op\":\"mir_call\"}" }
|
||||
payload = me._expect_map(payload, "mir_call payload")
|
||||
local dst_json = me._emit_vid_or_null(call("MapBox.get/2", inst, "dst"))
|
||||
local callee_json = me._emit_callee(call("MapBox.get/2", payload, "callee"))
|
||||
local args_box = call("MapBox.get/2", payload, "args")
|
||||
me._expect_array(args_box, "mir_call.args")
|
||||
local effects_box = call("MapBox.get/2", payload, "effects")
|
||||
me._expect_array(effects_box, "mir_call.effects")
|
||||
local args_json = me._emit_vid_array(args_box)
|
||||
local effects_json = me._emit_effects(effects_box)
|
||||
return "{\"op\":\"mir_call\",\"dst\":" + dst_json +
|
||||
",\"mir_call\":{\"callee\":" + callee_json +
|
||||
",\"args\":" + args_json +
|
||||
",\"effects\":" + effects_json + "}}"
|
||||
}
|
||||
// Fallback: emit op only (unexpected instruction kinds are outside P1 scope)
|
||||
return "{\"op\":" + me._quote(op) + "}"
|
||||
}
|
||||
|
||||
_emit_block(block) {
|
||||
if block == null { return "{\"id\":0,\"instructions\":[]}" }
|
||||
local insts = call("MapBox.get/2", block, "instructions")
|
||||
if insts == null {
|
||||
return "{\"id\":" + me._int_str(call("MapBox.get/2", block, "id")) + ",\"instructions\":[]}"
|
||||
}
|
||||
local n = BoxHelpers.array_len(insts)
|
||||
local body = me._emit_block_rec(insts, 0, n)
|
||||
return "{\"id\":" + me._int_str(call("MapBox.get/2", block, "id")) + ",\"instructions\":[" + body + "]}"
|
||||
}
|
||||
_emit_block_rec(insts, idx, len) {
|
||||
if idx >= len { return "" }
|
||||
local head = me._emit_inst(call("ArrayBox.get/2", insts, idx))
|
||||
local tail = me._emit_block_rec(insts, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
|
||||
_emit_function(func) {
|
||||
if func == null {
|
||||
return "{\"name\":\"main\",\"blocks\":[]}"
|
||||
}
|
||||
local name = call("MapBox.get/2", func, "name")
|
||||
if name == null { name = "main" }
|
||||
// Optional fields: params (array) / flags (map)
|
||||
local params = call("MapBox.get/2", func, "params")
|
||||
local flags = call("MapBox.get/2", func, "flags")
|
||||
local blocks = call("MapBox.get/2", func, "blocks")
|
||||
if blocks == null {
|
||||
if params != null || flags != null {
|
||||
local head = "{\"name\":" + me._quote(name)
|
||||
if params != null { head = head + ",\"params\":" + JSON.stringify(params) }
|
||||
if flags != null { head = head + ",\"flags\":" + JSON.stringify(flags) }
|
||||
return head + ",\"blocks\":[]}"
|
||||
}
|
||||
return "{\"name\":" + me._quote(name) + ",\"blocks\":[]}"
|
||||
}
|
||||
local n = BoxHelpers.array_len(blocks)
|
||||
local body = me._emit_function_rec(blocks, 0, n)
|
||||
local head2 = "{\"name\":" + me._quote(name)
|
||||
if params != null { head2 = head2 + ",\"params\":" + JSON.stringify(params) }
|
||||
if flags != null { head2 = head2 + ",\"flags\":" + JSON.stringify(flags) }
|
||||
return head2 + ",\"blocks\":[" + body + "]}"
|
||||
}
|
||||
_emit_function_rec(blocks, idx, len) {
|
||||
if idx >= len { return "" }
|
||||
local head = me._emit_block(call("ArrayBox.get/2", blocks, idx))
|
||||
local tail = me._emit_function_rec(blocks, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
|
||||
to_json(module) {
|
||||
if module == null { return "" }
|
||||
// Prefer single-function fallbackを強化: has() 未実装環境でも repr チェックで検出
|
||||
local f0 = call("MapBox.get/2", module, "functions_0")
|
||||
if f0 != null {
|
||||
local repr = "" + f0
|
||||
if repr.indexOf("MapBox(") == 0 || repr.indexOf("HostHandleBox(") == 0 {
|
||||
local one = me._emit_function(f0)
|
||||
return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[" + one + "]}"
|
||||
}
|
||||
}
|
||||
// Legacy path: try functions array if available
|
||||
local funcs = call("MapBox.get/2", module, "functions")
|
||||
if funcs == null { return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[]}" }
|
||||
local n = BoxHelpers.array_len(funcs)
|
||||
local body = me._emit_module_rec(funcs, 0, n)
|
||||
return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[" + body + "]}"
|
||||
}
|
||||
_emit_module_rec(funcs, idx, len) {
|
||||
// Defensive fallback: some environments report size=0 for host-managed arrays.
|
||||
// If len==0 at entry, try to probe index 0 once to recover a single element.
|
||||
if idx == 0 && len == 0 {
|
||||
local first = call("ArrayBox.get/2", funcs, 0)
|
||||
if first != null { len = 1 }
|
||||
}
|
||||
if idx >= len { return "" }
|
||||
local head = me._emit_function(call("ArrayBox.get/2", funcs, idx))
|
||||
local tail = me._emit_module_rec(funcs, idx + 1, len)
|
||||
if tail == "" { return head }
|
||||
return head + "," + tail
|
||||
}
|
||||
}
|
||||
104
lang/src/shared/mir/loop_form_box.hako
Normal file
104
lang/src/shared/mir/loop_form_box.hako
Normal file
@ -0,0 +1,104 @@
|
||||
// selfhost/shared/mir/loop_form_box.hako
|
||||
// LoopFormBox — minimal loop structure builder (P2: continue/break snapshots + Exit PHI)
|
||||
|
||||
using "lang/src/shared/mir/mir_schema_box.hako" as MirSchemaBox
|
||||
|
||||
static box LoopFormBox {
|
||||
|
||||
// while (i < limit) {
|
||||
// if (i == break_value) break;
|
||||
// if (i == skip_value) continue;
|
||||
// sum = sum + i;
|
||||
// i = i + 1;
|
||||
// }
|
||||
// Builds LoopForm structure with Header/Latch PHI + Exit PHI (continue/break aware)
|
||||
loop_counter(limit, skip_value, break_value) {
|
||||
if skip_value == null { skip_value = 2 }
|
||||
if break_value == null { break_value = limit }
|
||||
|
||||
// Preheader (block 0): initialise loop-carried values and constants, then jump header
|
||||
local pre = new ArrayBox()
|
||||
pre.push(MirSchemaBox.inst_const(1, 0)) // r1 = 0 (initial i)
|
||||
pre.push(MirSchemaBox.inst_const(2, limit)) // r2 = limit
|
||||
pre.push(MirSchemaBox.inst_const(3, 1)) // r3 = step
|
||||
pre.push(MirSchemaBox.inst_const(4, 0)) // r4 = sum
|
||||
pre.push(MirSchemaBox.inst_const(5, skip_value)) // r5 = continue trigger
|
||||
pre.push(MirSchemaBox.inst_const(6, break_value))// r6 = break trigger
|
||||
pre.push(MirSchemaBox.inst_jump(1)) // goto header
|
||||
|
||||
// Header (block 1): PHI(i), PHI(sum), compare, branch
|
||||
local header = new ArrayBox()
|
||||
local phi_i_inputs = new ArrayBox()
|
||||
phi_i_inputs.push(MirSchemaBox.phi_incoming(0, 1)) // from preheader
|
||||
phi_i_inputs.push(MirSchemaBox.phi_incoming(7, 18)) // from latch
|
||||
header.push(MirSchemaBox.inst_phi(10, phi_i_inputs)) // r10 = current i
|
||||
|
||||
local phi_sum_inputs = new ArrayBox()
|
||||
phi_sum_inputs.push(MirSchemaBox.phi_incoming(0, 4)) // from preheader
|
||||
phi_sum_inputs.push(MirSchemaBox.phi_incoming(7, 19)) // from latch
|
||||
header.push(MirSchemaBox.inst_phi(11, phi_sum_inputs))// r11 = current sum
|
||||
|
||||
header.push(MirSchemaBox.inst_compare("Lt", 10, 2, 12)) // r12 = (i < limit)
|
||||
header.push(MirSchemaBox.inst_branch(12, 2, 8)) // body or exit
|
||||
|
||||
// Body entry (block 2): if (i == break) -> break else check continue
|
||||
local body_check_break = new ArrayBox()
|
||||
body_check_break.push(MirSchemaBox.inst_compare("Eq", 10, 6, 13)) // r13 = (i == break)
|
||||
body_check_break.push(MirSchemaBox.inst_branch(13, 3, 4))
|
||||
|
||||
// Break path (block 3): jump directly to exit; exit PHI snapshots current sum
|
||||
local break_block = new ArrayBox()
|
||||
break_block.push(MirSchemaBox.inst_jump(8))
|
||||
|
||||
// Continue check (block 4): if (i == skip) -> continue else body work
|
||||
local continue_check = new ArrayBox()
|
||||
continue_check.push(MirSchemaBox.inst_compare("Eq", 10, 5, 14)) // r14 = (i == skip)
|
||||
continue_check.push(MirSchemaBox.inst_branch(14, 5, 6))
|
||||
|
||||
// Continue path (block 5): snapshot sum, increment i, jump latch
|
||||
local continue_block = new ArrayBox()
|
||||
continue_block.push(MirSchemaBox.inst_binop("Add", 10, 3, 15)) // r15 = i + step
|
||||
continue_block.push(MirSchemaBox.inst_jump(7))
|
||||
|
||||
// Body work (block 6): sum += i; i += step; jump latch
|
||||
local body_block = new ArrayBox()
|
||||
body_block.push(MirSchemaBox.inst_binop("Add", 11, 10, 16)) // r16 = sum + i
|
||||
body_block.push(MirSchemaBox.inst_binop("Add", 10, 3, 17)) // r17 = i + step
|
||||
body_block.push(MirSchemaBox.inst_jump(7))
|
||||
|
||||
// Latch (block 7): PHI merge for continue/normal, then jump header
|
||||
local latch_block = new ArrayBox()
|
||||
local latch_phi_i = new ArrayBox()
|
||||
latch_phi_i.push(MirSchemaBox.phi_incoming(5, 15)) // continue path
|
||||
latch_phi_i.push(MirSchemaBox.phi_incoming(6, 17)) // body path
|
||||
latch_block.push(MirSchemaBox.inst_phi(18, latch_phi_i)) // feeds header.i
|
||||
|
||||
local latch_phi_sum = new ArrayBox()
|
||||
latch_phi_sum.push(MirSchemaBox.phi_incoming(5, 11)) // continue keeps sum
|
||||
latch_phi_sum.push(MirSchemaBox.phi_incoming(6, 16)) // body updated sum
|
||||
latch_block.push(MirSchemaBox.inst_phi(19, latch_phi_sum)) // feeds header.sum
|
||||
latch_block.push(MirSchemaBox.inst_jump(1))
|
||||
|
||||
// Exit (block 8): Merge sums from header fallthrough and break edge, then return
|
||||
local exit_vals = new ArrayBox()
|
||||
exit_vals.push(MirSchemaBox.phi_incoming(1, 11)) // normal completion
|
||||
exit_vals.push(MirSchemaBox.phi_incoming(3, 11)) // break path
|
||||
local exit_block = new ArrayBox()
|
||||
exit_block.push(MirSchemaBox.inst_phi(20, exit_vals))
|
||||
exit_block.push(MirSchemaBox.inst_ret(20))
|
||||
|
||||
// Assemble blocks
|
||||
local blocks = new ArrayBox()
|
||||
blocks.push(MirSchemaBox.block(0, pre))
|
||||
blocks.push(MirSchemaBox.block(1, header))
|
||||
blocks.push(MirSchemaBox.block(2, body_check_break))
|
||||
blocks.push(MirSchemaBox.block(3, break_block))
|
||||
blocks.push(MirSchemaBox.block(4, continue_check))
|
||||
blocks.push(MirSchemaBox.block(5, continue_block))
|
||||
blocks.push(MirSchemaBox.block(6, body_block))
|
||||
blocks.push(MirSchemaBox.block(7, latch_block))
|
||||
blocks.push(MirSchemaBox.block(8, exit_block))
|
||||
|
||||
return MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
|
||||
}
|
||||
}
|
||||
216
lang/src/shared/mir/mir_io_box.hako
Normal file
216
lang/src/shared/mir/mir_io_box.hako
Normal file
@ -0,0 +1,216 @@
|
||||
// mir_io_box.hako — MirIoBox (Phase A, Hako-side implementation)
|
||||
// Responsibility: provide minimal read/validate/cursor API using existing locator boxes.
|
||||
// Provider selection:
|
||||
// - Default: scan (string scanning, no plugin required)
|
||||
// - Optional: yyjson (JsonDoc/JsonNode via HostBridge extern) when JSON plugin is available
|
||||
// Fail‑Fast:
|
||||
// - validate() checks kind/schema/functions
|
||||
// - validate_function(): terminator required + jump/branch target existence (scan path ensured; provider path WIP)
|
||||
|
||||
using "lang/src/vm/boxes/result_box.hako" as Result
|
||||
using "lang/src/vm/boxes/result_helpers.hako" as ResultHelpers
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/shared/json/json_canonical_box.hako" as JsonCanonicalBox
|
||||
using "lang/src/vm/hakorune-vm/function_locator.hako" as FunctionLocatorBox
|
||||
using "lang/src/vm/hakorune-vm/blocks_locator.hako" as BlocksLocatorBox
|
||||
using "lang/src/vm/hakorune-vm/instrs_locator.hako" as InstrsLocatorBox
|
||||
using "lang/src/vm/hakorune-vm/backward_object_scanner.hako" as BackwardObjectScannerBox
|
||||
using "lang/src/vm/hakorune-vm/block_iterator.hako" as BlockIteratorBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
|
||||
|
||||
static box MirIoBox {
|
||||
// Internal: provider gate (yyjson)
|
||||
_provider_gate_on() {
|
||||
// Accept HAKO_JSON_PROVIDER or NYASH_JSON_PROVIDER (alias). Value: 'yyjson'
|
||||
// Use extern env.local.get to avoid direct field access to `env`.
|
||||
local v = call("env.local.get/1", "HAKO_JSON_PROVIDER")
|
||||
if v == null || v == "" { v = call("env.local.get/1", "NYASH_JSON_PROVIDER") }
|
||||
if v == null || v == "" { return 0 }
|
||||
if v == "yyjson" || v == "YYJSON" { return 1 }
|
||||
return 0
|
||||
}
|
||||
// Provider policy
|
||||
// - Default path uses string scanning (no plugin dependency)
|
||||
// - When JSON provider (yyjson) is available via HostBridge, provider path becomes eligible
|
||||
// - This box must remain Fail-Fast: invalid kind/schema/functions should be rejected deterministically
|
||||
// - Provider enablement is transparent; do not add silent fallbacks
|
||||
// Return canonicalized full MIR JSON (keys sorted, arrays preserve order)
|
||||
normalize(json) {
|
||||
return JsonCanonicalBox.canonicalize(json)
|
||||
}
|
||||
// Provider selection: prefer JSON provider when available; otherwise use scan locators
|
||||
// 判定は _json_root() の成否で行う(ENV不要)。
|
||||
|
||||
|
||||
|
||||
// Helper: parse JSON and return root JsonNodeBox (provider=yyjson)
|
||||
_json_root(json) {
|
||||
// Minimal provider (default OFF). When ON, attempt JsonDocBox.parse+root via HostBridge.
|
||||
if me._provider_gate_on() == 0 { return null }
|
||||
if json == null { return Result.Err("json provider: input is null") }
|
||||
// Construct JsonDocBox without ArrayBox dependency
|
||||
local doc = hostbridge.box_new0("JsonDocBox")
|
||||
if doc == null { return Result.Err("json provider: box_new failed") }
|
||||
// parse(json) — 1-arg convenience wrapper
|
||||
hostbridge.box_call1(doc, "parse", json)
|
||||
// check error()
|
||||
local err = hostbridge.box_call0(doc, "error")
|
||||
if err != null && err != "" { return Result.Err("json provider: parse failed: " + err) }
|
||||
// root() — existence確認のみ(成功時は Ok(0) を返す)
|
||||
local node = hostbridge.box_call0(doc, "root")
|
||||
if node == null { return Result.Err("json provider: root failed") }
|
||||
return Result.Ok(0)
|
||||
}
|
||||
validate(json) {
|
||||
if json == null { return Result.Err("null json") }
|
||||
// Optional: canonicalize full MIR JSON at ingress (identity/no-op for now)
|
||||
json = JsonCanonicalBox.canonicalize(json)
|
||||
// Accept full root (preferred)
|
||||
if json.indexOf("\"kind\":\"MIR\"") >= 0 {
|
||||
if json.indexOf("\"schema_version\":\"1.0\"") < 0 { return Result.Err("invalid schema_version") }
|
||||
if json.indexOf("\"functions\"") < 0 { return Result.Err("functions missing") }
|
||||
return Result.Ok(0)
|
||||
}
|
||||
// Tolerate narrowed JSON (single function object) used by nyvm bridge
|
||||
if json.indexOf("\"instructions\"") >= 0 && json.indexOf("\"id\"") >= 0 {
|
||||
return Result.Ok(0)
|
||||
}
|
||||
return Result.Err("invalid kind")
|
||||
}
|
||||
|
||||
// Return function JSON meta (content)
|
||||
functions(json) {
|
||||
// Canonicalize full root when present (identity until host bridge is wired)
|
||||
json = JsonCanonicalBox.canonicalize(json)
|
||||
// Provider disabled: always use scan locator
|
||||
local loc = FunctionLocatorBox.locate(json)
|
||||
if loc.is_Err() { return loc }
|
||||
return loc
|
||||
}
|
||||
|
||||
// Return blocks[] meta (content without brackets)
|
||||
blocks(func_json) {
|
||||
// Provider disabled: always use scan locator
|
||||
local loc = BlocksLocatorBox.locate(func_json)
|
||||
if loc.is_Err() { return loc }
|
||||
return loc
|
||||
}
|
||||
|
||||
// Return instructions content and single flag
|
||||
instructions(block_json) {
|
||||
// Provider disabled: always use scan locator
|
||||
local loc = InstrsLocatorBox.locate(block_json)
|
||||
if loc.is_Err() { return loc }
|
||||
return loc
|
||||
}
|
||||
|
||||
// Return last terminator object (as string)
|
||||
terminator(block_json) {
|
||||
// Provider disabled: use scan locator then parse last object
|
||||
local loc = InstrsLocatorBox.locate(block_json)
|
||||
if loc.is_Err() { return loc }
|
||||
local meta = loc.as_Ok()
|
||||
local insts = BoxHelpers.map_get(meta, "content")
|
||||
if insts == "" { return Result.Err("terminator not found") }
|
||||
local single = BoxHelpers.map_get(meta, "single")
|
||||
if single != null && single == 1 { return Result.Ok(insts) }
|
||||
// use small window for large JSON (8192 bytes)
|
||||
local last = BackwardObjectScannerBox.scan_last_object_opt_window(insts, 200000, 8192)
|
||||
if last.is_Err() { return Result.Err("terminator scan failed: " + last.as_Err()) }
|
||||
return Result.Ok(last.as_Ok())
|
||||
}
|
||||
|
||||
// Strict validate for function JSON (narrow): terminator required and references valid
|
||||
validate_function(func_json) {
|
||||
// collect block ids
|
||||
local bl = me.blocks(func_json)
|
||||
if bl.is_Err() { return bl }
|
||||
local meta = bl.as_Ok()
|
||||
local content = BoxHelpers.map_get(meta, "content")
|
||||
local ids = new MapBox()
|
||||
// iterate blocks
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local it = BlockIteratorBox.next(content, pos)
|
||||
if it.is_Err() { break }
|
||||
local m = it.as_Ok()
|
||||
local obj = BoxHelpers.map_get(m, "obj")
|
||||
pos = BoxHelpers.map_get(m, "next_pos")
|
||||
// parse id
|
||||
local key_id = "\"id\":"
|
||||
local p = obj.indexOf(key_id)
|
||||
if p < 0 { return Result.Err("block id missing") }
|
||||
p = p + key_id.size()
|
||||
// skip ws
|
||||
loop(p < obj.size()) { local ch = obj.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
|
||||
local digs = StringHelpers.read_digits(obj, p)
|
||||
if digs == "" { return Result.Err("invalid block id") }
|
||||
ids.set(StringHelpers.int_to_str(StringHelpers.to_i64(digs)), 1)
|
||||
// require terminator
|
||||
if obj.indexOf("\"terminator\":{") < 0 { return Result.Err("terminator missing") }
|
||||
}
|
||||
// validate each block terminator references
|
||||
pos = 0
|
||||
loop(true) {
|
||||
local it = BlockIteratorBox.next(content, pos)
|
||||
if it.is_Err() { break }
|
||||
local m = it.as_Ok()
|
||||
pos = BoxHelpers.map_get(m, "next_pos")
|
||||
local obj = BoxHelpers.map_get(m, "obj")
|
||||
// extract terminator object
|
||||
local ts = obj.indexOf("\"terminator\":{")
|
||||
if ts < 0 { return Result.Err("terminator missing") }
|
||||
local te = ts + "\"terminator\":".size()
|
||||
// naive: use general locator to get terminator via instructions fallback if needed
|
||||
local term = me.terminator(obj)
|
||||
if term.is_Err() { return Result.Err("terminator parse failed") }
|
||||
local tj = term.as_Ok()
|
||||
// op tolerant
|
||||
local k = "\"op\""
|
||||
local p2 = tj.indexOf(k)
|
||||
if p2 < 0 { return Result.Err("terminator op not found") }
|
||||
p2 = p2 + k.size()
|
||||
loop(p2 < tj.size()) { local ch2 = tj.substring(p2,p2+1) if ch2 == ":" { p2 = p2 + 1 break } if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { p2 = p2 + 1 continue } return Result.Err("terminator op colon not found") }
|
||||
loop(p2 < tj.size()) { local ch3 = tj.substring(p2,p2+1) if ch3 == " " || ch3 == "\n" || ch3 == "\r" || ch3 == "\t" { p2 = p2 + 1 continue } break }
|
||||
if tj.substring(p2,p2+1) != "\"" { return Result.Err("terminator op quote not found") }
|
||||
local e2 = JsonCursorBox.index_of_from(tj, "\"", p2+1)
|
||||
if e2 < 0 { return Result.Err("terminator op end not found") }
|
||||
local op = tj.substring(p2+1, e2)
|
||||
if op == "jump" {
|
||||
local kt = "\"target\":"
|
||||
local pt = tj.indexOf(kt)
|
||||
if pt < 0 { return Result.Err("jump: target missing") }
|
||||
pt = pt + kt.size()
|
||||
loop(pt < tj.size()) {
|
||||
local ch = tj.substring(pt,pt+1)
|
||||
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { pt = pt + 1 continue }
|
||||
break
|
||||
}
|
||||
local digs2 = StringHelpers.read_digits(tj, pt)
|
||||
if digs2 == "" { return Result.Err("jump: invalid target") }
|
||||
local key = StringHelpers.int_to_str(StringHelpers.to_i64(digs2))
|
||||
if call("MapBox.get/2", ids, key) == null { return Result.Err("jump: unknown target") }
|
||||
} else { if op == "branch" {
|
||||
local k1 = "\"then_bb\":"
|
||||
local p3 = tj.indexOf(k1)
|
||||
if p3 < 0 { return Result.Err("branch: then_bb missing") }
|
||||
p3 = p3 + k1.size()
|
||||
loop(p3 < tj.size()) { local ch = tj.substring(p3,p3+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p3 = p3 + 1 continue } break }
|
||||
local d1 = StringHelpers.read_digits(tj, p3)
|
||||
if d1 == "" { return Result.Err("branch: invalid then_bb") }
|
||||
local k2 = "\"else_bb\":"
|
||||
local p4 = tj.indexOf(k2)
|
||||
if p4 < 0 { return Result.Err("branch: else_bb missing") }
|
||||
p4 = p4 + k2.size()
|
||||
loop(p4 < tj.size()) { local ch = tj.substring(p4,p4+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p4 = p4 + 1 continue } break }
|
||||
local d2 = StringHelpers.read_digits(tj, p4)
|
||||
if d2 == "" { return Result.Err("branch: invalid else_bb") }
|
||||
if call("MapBox.get/2", ids, StringHelpers.int_to_str(StringHelpers.to_i64(d1))) == null { return Result.Err("branch: unknown then_bb") }
|
||||
if call("MapBox.get/2", ids, StringHelpers.int_to_str(StringHelpers.to_i64(d2))) == null { return Result.Err("branch: unknown else_bb") }
|
||||
} else { if op == "ret" { /* ok */ } else { return Result.Err("terminator: unsupported op "" + op + """) } }
|
||||
}
|
||||
}
|
||||
return Result.Ok(0)
|
||||
}
|
||||
}
|
||||
243
lang/src/shared/mir/mir_schema_box.hako
Normal file
243
lang/src/shared/mir/mir_schema_box.hako
Normal file
@ -0,0 +1,243 @@
|
||||
// selfhost/shared/mir/mir_schema_box.hako
|
||||
// MirSchemaBox — minimal MIR(JSON v0) constructors (P1 scope)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box MirSchemaBox {
|
||||
_expect_map(val, context) {
|
||||
if val == null {
|
||||
print("[MirSchemaBox] dev assert failed: expected MapBox (non-null) for " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_map_null")
|
||||
return val
|
||||
}
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 { return val }
|
||||
print("[MirSchemaBox] dev assert failed: expected MapBox for " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_map")
|
||||
return val
|
||||
}
|
||||
_expect_array(val, context) {
|
||||
if val == null {
|
||||
print("[MirSchemaBox] dev assert failed: expected ArrayBox (non-null) for " + context)
|
||||
call("ArrayBox.get/2", val, 0)
|
||||
return val
|
||||
}
|
||||
local repr = "" + val
|
||||
if repr.indexOf("ArrayBox(") == 0 { return val }
|
||||
print("[MirSchemaBox] dev assert failed: expected ArrayBox for " + context)
|
||||
call("ArrayBox.get/2", val, 0)
|
||||
return val
|
||||
}
|
||||
_expect_i64(val, context) {
|
||||
if val == null {
|
||||
print("[MirSchemaBox] dev assert failed: expected i64 (non-null) for " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_i64_null")
|
||||
return 0
|
||||
}
|
||||
local repr = "" + val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local ty = call("MapBox.get/2", val, "type")
|
||||
if ty != null {
|
||||
local ty_str = "" + ty
|
||||
if ty_str != "i64" && ty_str != "int" && ty_str != "integer" {
|
||||
print("[MirSchemaBox] dev assert failed: unexpected type " + ty_str + " in " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_i64_type")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
local inner = call("MapBox.get/2", val, "value")
|
||||
if inner != null { return StringHelpers.to_i64(inner) }
|
||||
print("[MirSchemaBox] dev assert failed: missing value in " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_i64_value")
|
||||
return 0
|
||||
}
|
||||
if StringHelpers.is_numeric_str("" + val) == 1 { return StringHelpers.to_i64(val) }
|
||||
print("[MirSchemaBox] dev assert failed: expected numeric value for " + context)
|
||||
call("MapBox.get/2", val, "__mir_schema_expect_i64_direct")
|
||||
return 0
|
||||
}
|
||||
_len(arr) {
|
||||
if arr == null { return 0 }
|
||||
// ArrayBox.size/1 は MapBox-wrapped integer を返すので、unwrap する
|
||||
local size_val = call("ArrayBox.size/1", arr)
|
||||
local repr = "" + size_val
|
||||
if repr.indexOf("MapBox(") == 0 {
|
||||
local inner = call("MapBox.get/2", size_val, "value")
|
||||
if inner != null { return inner }
|
||||
}
|
||||
return size_val
|
||||
}
|
||||
// Scalars (normalize to i64 for P1)
|
||||
i(x) {
|
||||
// Return MapBox { type: "i64", value: x }
|
||||
local m = new MapBox()
|
||||
m.set("type", "i64")
|
||||
m.set("value", x)
|
||||
return m
|
||||
}
|
||||
|
||||
// Instructions
|
||||
inst_const(dst, val) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "const")
|
||||
m.set("dst", this.i(dst))
|
||||
m.set("value", this.i(val))
|
||||
return m
|
||||
}
|
||||
inst_ret(val) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "ret")
|
||||
m.set("value", this.i(val))
|
||||
return m
|
||||
}
|
||||
inst_compare(cmp, lhs, rhs, dst) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "compare")
|
||||
m.set("cmp", cmp)
|
||||
m.set("lhs", this.i(lhs))
|
||||
m.set("rhs", this.i(rhs))
|
||||
m.set("dst", this.i(dst))
|
||||
return m
|
||||
}
|
||||
inst_binop(kind, lhs, rhs, dst) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "binop")
|
||||
m.set("op_kind", kind)
|
||||
m.set("lhs", this.i(lhs))
|
||||
m.set("rhs", this.i(rhs))
|
||||
m.set("dst", this.i(dst))
|
||||
return m
|
||||
}
|
||||
inst_branch(cond, then_id, else_id) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "branch")
|
||||
m.set("cond", this.i(cond))
|
||||
m.set("then", this.i(then_id))
|
||||
m.set("else", this.i(else_id))
|
||||
return m
|
||||
}
|
||||
inst_jump(target) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "jump")
|
||||
m.set("target", this.i(target))
|
||||
return m
|
||||
}
|
||||
|
||||
// Phi instruction (v0 schema)
|
||||
phi_incoming(block_id, value_id) {
|
||||
local m = new MapBox()
|
||||
m.set("block", this.i(block_id))
|
||||
m.set("value", this.i(value_id))
|
||||
return m
|
||||
}
|
||||
inst_phi(dst, incoming) {
|
||||
local m = new MapBox()
|
||||
m.set("op", "phi")
|
||||
m.set("dst", this.i(dst))
|
||||
local arr = new ArrayBox()
|
||||
if incoming != null {
|
||||
local n = me._len(incoming)
|
||||
local i = 0
|
||||
loop(i < n) {
|
||||
arr.push(call("ArrayBox.get/2", incoming, i))
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
m.set("values", arr)
|
||||
return m
|
||||
}
|
||||
|
||||
// Unified call (mir_call) variants — minimal P3
|
||||
_wrap_ids(arr) {
|
||||
local out = new ArrayBox()
|
||||
if arr == null { return out }
|
||||
me._expect_array(arr, "mir_call args source")
|
||||
local i = 0
|
||||
local n = me._len(arr)
|
||||
loop(i < n) {
|
||||
out.push(this.i(call("ArrayBox.get/2", arr, i)))
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
inst_mir_call_extern(name, arg_ids, dst) {
|
||||
local callee = new MapBox(); callee.set("type", "Extern"); callee.set("name", name)
|
||||
local payload = new MapBox()
|
||||
payload.set("callee", callee)
|
||||
payload.set("args", this._wrap_ids(arg_ids))
|
||||
payload.set("effects", new ArrayBox())
|
||||
local m = new MapBox()
|
||||
m.set("op", "mir_call")
|
||||
m.set("dst", this.i(dst))
|
||||
me._expect_map(payload, "mir_call payload (extern)")
|
||||
m.set("mir_call", payload)
|
||||
return m
|
||||
}
|
||||
inst_mir_call_global(name, arg_ids, dst) {
|
||||
local callee = new MapBox(); callee.set("type", "Global"); callee.set("name", name)
|
||||
local payload = new MapBox()
|
||||
payload.set("callee", callee)
|
||||
payload.set("args", this._wrap_ids(arg_ids))
|
||||
payload.set("effects", new ArrayBox())
|
||||
local m = new MapBox()
|
||||
m.set("op", "mir_call")
|
||||
m.set("dst", this.i(dst))
|
||||
me._expect_map(payload, "mir_call payload (global)")
|
||||
m.set("mir_call", payload)
|
||||
return m
|
||||
}
|
||||
inst_mir_call_method(method, recv_id, arg_ids, dst) {
|
||||
local callee = new MapBox()
|
||||
callee.set("type", "Method")
|
||||
callee.set("method", method)
|
||||
callee.set("receiver", this.i(recv_id))
|
||||
local m = new MapBox()
|
||||
m.set("op", "mir_call")
|
||||
m.set("dst", this.i(dst))
|
||||
local payload = new MapBox()
|
||||
payload.set("callee", callee)
|
||||
payload.set("args", this._wrap_ids(arg_ids))
|
||||
payload.set("effects", new ArrayBox())
|
||||
payload = me._expect_map(payload, "mir_call payload (method)")
|
||||
m.set("mir_call", payload)
|
||||
return m
|
||||
}
|
||||
inst_mir_call_constructor(box_type, arg_ids, dst) {
|
||||
local callee = new MapBox(); callee.set("type", "Constructor"); callee.set("box_type", box_type)
|
||||
local m = new MapBox()
|
||||
m.set("op", "mir_call")
|
||||
m.set("dst", this.i(dst))
|
||||
local payload = new MapBox()
|
||||
payload.set("callee", callee)
|
||||
payload.set("args", this._wrap_ids(arg_ids))
|
||||
payload.set("effects", new ArrayBox())
|
||||
me._expect_map(payload, "mir_call payload (constructor)")
|
||||
m.set("mir_call", payload)
|
||||
return m
|
||||
}
|
||||
|
||||
// Block/Module
|
||||
block(id, insts) {
|
||||
local m = new MapBox()
|
||||
m.set("id", this.i(id))
|
||||
m.set("instructions", insts)
|
||||
return m
|
||||
}
|
||||
fn_main(blocks) {
|
||||
local m = new MapBox()
|
||||
m.set("name", "main")
|
||||
m.set("blocks", blocks)
|
||||
return m
|
||||
}
|
||||
module(fn_main) {
|
||||
local m = new MapBox()
|
||||
m.set("version", this.i(0))
|
||||
m.set("kind", "MIR")
|
||||
// Avoid push() to sidestep host-slot gating; build as literal for determinism
|
||||
local funcs = [ fn_main ]
|
||||
m.set("functions", funcs)
|
||||
// Fallback access path for environments where Array.size/1 is unreliable
|
||||
m.set("functions_0", fn_main)
|
||||
return m
|
||||
}
|
||||
}
|
||||
8
lang/src/tools/loader_front/LAYER_GUARD.hako
Normal file
8
lang/src/tools/loader_front/LAYER_GUARD.hako
Normal file
@ -0,0 +1,8 @@
|
||||
#![doc = "Phase 20.13 loader_front guard — define layer responsibilities."]
|
||||
|
||||
static box LOADER_FRONT_GUARD {
|
||||
name() { return "loader_front" }
|
||||
allowed_imports() { return ["json", "env"] }
|
||||
forbidden_imports() { return ["vm_core", "plugins", "ffi"] }
|
||||
}
|
||||
|
||||
27
lang/src/tools/loader_front/README.md
Normal file
27
lang/src/tools/loader_front/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# loader_front — Loader Front EXE (Phase 20.13)
|
||||
|
||||
Purpose
|
||||
- Decide high-level loading (policy/allowlist/resolution) ahead of kernel loader.
|
||||
- Output a short, stable verdict; kernel honors it strictly (no fallback).
|
||||
|
||||
Responsibilities
|
||||
- Evaluate env/policy and candidate modules.
|
||||
- Emit: OK/NOOP/FAIL (tagged), exit code semantics same as runner_front.
|
||||
|
||||
Inputs/Outputs
|
||||
- Input: JSON/CLI with policy keys and candidates
|
||||
- Output: one-line verdict (short) + exit code
|
||||
|
||||
ENV (planned; default OFF)
|
||||
- HAKO_LOADER_USE_SCRIPT_EXE=1 (alias NYASH_*) — enable loader front EXE
|
||||
- HAKO_QUIET=1 — quiet mode adherence
|
||||
|
||||
Non‑Goals
|
||||
- No filesystem/ABI side-effects beyond read-only inspection.
|
||||
|
||||
TTL
|
||||
- Diagnostics/formatting migrates from Rust boundary to this front EXE when stable.
|
||||
|
||||
Notes (AOT / Single‑EXE)
|
||||
- 将来的に単一exeに内蔵(AOT)され、ENV反映→通常ロード継続を同一プロセスで行います。
|
||||
- 開発時は tools/front_exe/loader_front.sh をゲートONで差し替え可能です。
|
||||
8
lang/src/tools/runner_front/LAYER_GUARD.hako
Normal file
8
lang/src/tools/runner_front/LAYER_GUARD.hako
Normal file
@ -0,0 +1,8 @@
|
||||
#![doc = "Phase 20.13 runner_front guard — define layer responsibilities."]
|
||||
|
||||
static box RUNNER_FRONT_GUARD {
|
||||
name() { return "runner_front" }
|
||||
allowed_imports() { return ["json", "cli", "env"] }
|
||||
forbidden_imports() { return ["vm_core", "llvm", "plugins"] }
|
||||
}
|
||||
|
||||
32
lang/src/tools/runner_front/README.md
Normal file
32
lang/src/tools/runner_front/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# runner_front — Script-built Front EXE (Phase 20.13)
|
||||
|
||||
Purpose
|
||||
- Decide runner mode and normalize inputs before core execution.
|
||||
- Replace Rust-side front logic via a thin, testable EXE (AOT built from Hakorune scripts).
|
||||
|
||||
Notes (AOT / Single‑EXE)
|
||||
- 本モジュールは将来、単一の hakorune.exe に内蔵(AOT)され、同一プロセス内で呼び出されます。
|
||||
- 開発時は tools/front_exe/runner_front.sh をゲートONで差し替え可能(1行契約の互換を維持)。
|
||||
|
||||
Responsibilities
|
||||
- Parse CLI/ENV relevant to backend/mode/entry.
|
||||
- Produce a short verdict and exit code:
|
||||
- OK: adopt decision (prints normalized JSON or token) and exit 0
|
||||
- NOOP: no decision; Rust runner proceeds with legacy path, exit 0
|
||||
- FAIL: stable tag, exit non‑zero (no fallback by design)
|
||||
|
||||
Inputs/Outputs (contract)
|
||||
- Input: CLI args + ENV (documented below)
|
||||
- Output: one short line (OK/NOOP/FAIL + payload or tag) to stdout
|
||||
- Exit codes: 0=adopt/noop, 1=fail
|
||||
|
||||
ENV (planned; default OFF)
|
||||
- HAKO_RUNNER_USE_SCRIPT_EXE=1 (alias NYASH_*) — enable front EXE adoption
|
||||
- HAKO_QUIET=1 — silence noisy logs (front adheres to quiet)
|
||||
|
||||
Non‑Goals
|
||||
- No plugin/ABI routing here (handled downstream)
|
||||
- No semantics; runner_front only decides and normalizes
|
||||
|
||||
TTL / Removal Plan
|
||||
- Runner boundary diagnostics/tags are temporary; migrate to core/front over time.
|
||||
8
lang/src/tools/vm_front/LAYER_GUARD.hako
Normal file
8
lang/src/tools/vm_front/LAYER_GUARD.hako
Normal file
@ -0,0 +1,8 @@
|
||||
#![doc = "Phase 20.13 vm_front guard — define layer responsibilities."]
|
||||
|
||||
static box VM_FRONT_GUARD {
|
||||
name() { return "vm_front" }
|
||||
allowed_imports() { return ["json", "env"] }
|
||||
forbidden_imports() { return ["vm_core", "llvm", "plugins", "ffi"] }
|
||||
}
|
||||
|
||||
26
lang/src/tools/vm_front/README.md
Normal file
26
lang/src/tools/vm_front/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# vm_front — VM Front EXE (Phase 20.13; optional)
|
||||
|
||||
Purpose
|
||||
- Provide light dispatch hints/normalization prior to VM/LLVM execution.
|
||||
- Keep semantics in VM/LLVM; vm_front only shapes inputs and rejects invalid cases early.
|
||||
|
||||
Responsibilities
|
||||
- Normalize callee/method hints; produce OK/NOOP/FAIL (short) with exit code.
|
||||
|
||||
Notes (AOT / Single‑EXE)
|
||||
- 将来的に単一exeに内蔵(AOT)され、同一プロセスで前段整流を行います。
|
||||
- 開発時は tools/front_exe/vm_front.sh をゲートONで差し替え可能です。
|
||||
|
||||
Inputs/Outputs
|
||||
- Input: small JSON describing callsite/callee
|
||||
- Output: one-line verdict (OK/NOOP/FAIL) + exit code
|
||||
|
||||
ENV (planned; default OFF)
|
||||
- HAKO_VM_USE_SCRIPT_EXE=1 (alias NYASH_*) — enable vm front EXE
|
||||
- HAKO_QUIET=1
|
||||
|
||||
Non‑Goals
|
||||
- No execution, no state, no ABI interaction.
|
||||
|
||||
TTL
|
||||
- Runner-side formatting will migrate here after stabilization.
|
||||
15
lang/src/vm/DEPRECATED.md
Normal file
15
lang/src/vm/DEPRECATED.md
Normal file
@ -0,0 +1,15 @@
|
||||
# DEPRECATED: selfhost/vm (Mini‑VM sandbox)
|
||||
|
||||
This directory hosts the original Mini‑VM used for early self‑hosting.
|
||||
|
||||
Policy
|
||||
- Status: Frozen (no new features).
|
||||
- Purpose: Dev/education and targeted repros only.
|
||||
- Successor: selfhost/hakorune-vm (Hakorune VM) — nyvm maps here by default.
|
||||
|
||||
Migration
|
||||
- Prefer using aliases under `selfhost.hakorune-vm.*`.
|
||||
- Mini‑VM specific smokes remain rc-only and opt-in.
|
||||
|
||||
Removal trigger
|
||||
- When Hakorune VM reaches feature parity and quick/integration remain green for one sprint, Mini‑VM will be retired.
|
||||
6
lang/src/vm/LAYER_GUARD.hako
Normal file
6
lang/src/vm/LAYER_GUARD.hako
Normal file
@ -0,0 +1,6 @@
|
||||
// LAYER_GUARD — VM Layer Guard (Phase 20.8)
|
||||
// Policy:
|
||||
// - lang/src 優先。lang/src 配下からの `using "selfhost/..."` 直参照は禁止。
|
||||
// - 参照は lang/src の等価箱(ミラー)へ統一し、段階撤退を進める。
|
||||
// - Mini‑VM の新規機能追加は慎重に(既定は凍結・スモークで仕様固定)。
|
||||
static box MiniVmLayerGuard { main(args) { return 0 } }
|
||||
105
lang/src/vm/README.md
Normal file
105
lang/src/vm/README.md
Normal file
@ -0,0 +1,105 @@
|
||||
# VM Layout (Current → Target)
|
||||
|
||||
Current
|
||||
- `lang/src/vm/hakorune-vm/` — Hakorune VM (nyvm) implementation
|
||||
- `lang/src/vm/boxes/` — Shared helpers (op_handlers, scanners, compare, etc.)
|
||||
- Mini‑VM minimal executor lives as boxes (e.g., `boxes/mir_vm_min.hako`)
|
||||
|
||||
Target (post‑20.12b, gradual)
|
||||
- `engines/hakorune/` — mainline nyvm engine
|
||||
- `engines/mini/` — Mini‑VM engine (educational/minimal)
|
||||
- `boxes/` — shared helpers
|
||||
- `core/` — centralized execution core (value/state/reader/dispatcher + ops)
|
||||
|
||||
Policy
|
||||
- Engines orchestrate execution and may depend on boxes and shared/*.
|
||||
- Boxes are pure helpers (no engine loop, no I/O, no plugin/ABI).
|
||||
- Parser/Resolver/Emitter must not be imported from engines/boxes.
|
||||
- Core provides engine‑agnostic execution primitives and should not import
|
||||
engine‑specific modules. During migration, temporary adapters may exist.
|
||||
|
||||
Bridge‑B (Ny/Core 直行)
|
||||
- Wrapper 経路では `include "lang/src/vm/core/dispatcher.hako"` で Core Dispatcher を取り込み、
|
||||
`NyVmDispatcher.run(json)` を直接呼び出す。`using` は名前解決のみで実体は登録されないため、
|
||||
Core を呼ぶ目的では `include` を用いること。
|
||||
Gate‑C(Core) 直行(`NYASH_GATE_C_CORE=1`)は JSON→Core Interpreter 実行なのでこの問題の影響を受けない。
|
||||
|
||||
Toggles and Canaries
|
||||
- Core canaries (quick profile): enable with `SMOKES_ENABLE_CORE_CANARY=1`.
|
||||
- Emit→nyvm(Core) scripts: `tools/smokes/v2/profiles/quick/core/canary_emit_nyvm_core_{return,binop,if}_vm.sh`
|
||||
- Gate‑C(Core, json→Core 直行) canaries: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh`(既定OFF)
|
||||
- Gate‑C(Core) array sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.sh`(push→set→get をログで検証)
|
||||
- Gate‑C(Core) map sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_map_{len,iterator}_vm.sh`
|
||||
- Emit→Core map len/get: `tools/smokes/v2/profiles/quick/core/canary_emit_core_map_len_get_vm.sh`
|
||||
- Gate‑C Direct sanity: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_direct_string_vm.sh`
|
||||
- Runner Core toggle: `HAKO_NYVM_CORE=1` (or `NYASH_NYVM_CORE=1`) selects the
|
||||
Core bridge for the nyvm wrapper path.
|
||||
- Gate‑C Core route: set `NYASH_GATE_C_CORE=1` (or `HAKO_GATE_C_CORE=1`) to
|
||||
execute MIR(JSON v0) directly via Core (interpreter path; quiet; exit code mirrors return).
|
||||
- Env: `NYASH_CORE_MAX_ITERS` or `HAKO_CORE_MAX_ITERS` overrides the Core dispatcher loop cap (default 10000).
|
||||
- Plugins: when `HAKO_GATE_C_ENABLE_PLUGINS=1` is set, the runner normalizes
|
||||
Array/Map core methods through HostHandleRouter (`HAKO_ARRAY_FORCE_HOST=1`,
|
||||
`HAKO_MAP_FORCE_HOST=1`) to keep value/return semantics stable. Plugins が OFF のときは
|
||||
ビルトインの `ArrayBox` / `MapBox` にフォールバックする。Map の `len()/size()` は extern adapter が
|
||||
ビルトイン `MapBox` の内部データ長を返すフォールバックを持つため(plugins=OFF でも)0 固定にはならない。
|
||||
- Errors: VM 実行/JSON読込エラー時は非0で終了(Fail‑Fast)。
|
||||
|
||||
Deprecations
|
||||
- `NYASH_GATE_C_DIRECT` は移行中の互換トグル(TTL)だよ。将来は Gate‑C(Core)
|
||||
直行(`HAKO_GATE_C_CORE=1`)に統一予定。新しい導線では Core の実行仕様(数値=rc,
|
||||
安定化した診断タグ)が適用されるよ。
|
||||
- 互換トグルを使うと起動時に警告が出るよ(`HAKO_GATE_C_DIRECT_SILENCE=1` で抑止可)。
|
||||
|
||||
Diagnostics (stable tags)
|
||||
- 本フェーズでは、Gate‑C(Core) の境界で安定タグを整形して出力する:
|
||||
- `[core/binop] div by zero`
|
||||
- `[core/mir_call] array get out of bounds`
|
||||
- `[core/mir_call] array set out of bounds`
|
||||
- `[core/mir_call] modulefn unsupported: …`
|
||||
- `[core/mir_call] map iterator unsupported`
|
||||
- `[core/mir_call] map len missing arg`
|
||||
- `[core/mir_call] map set missing key|bad key|missing value|bad value`
|
||||
- `[core/mir_call] map get missing key|bad key`
|
||||
- `[core/mir_call] unsupported callee type: Closure`
|
||||
- Gate‑C Direct では、リーダー/検証レイヤの診断をそのまま用いる(例: `unsupported callee type (expected Extern): ModuleFunction`)。
|
||||
|
||||
Exit code differences
|
||||
- Core: 数値=rc(OS仕様により 0–255 に丸められる。例: 777 → rc=9)、エラーは非0
|
||||
- Direct: 数値出力のみ(rc=0)、エラーは非0
|
||||
- 数値が 255 を超えるケースは標準出力の値で検証すること(rc は下位8ビットへ丸められるため)
|
||||
|
||||
注: これらの整形は移行中の暫定仕様で、最終的には Core 側に移管される予定(CURRENT_TASK に TTL を記載)。
|
||||
|
||||
Minimal mir_call semantics (Core)
|
||||
- Implemented (scoped):
|
||||
- Constructor: `ArrayBox`(サイズメタ初期化) / `MapBox`(エントリ数メタ初期化)
|
||||
- Methods (Array): `size()/push()/pop()/get()/set()`(メタデータでサイズ検証)
|
||||
- Methods (Map): `size()/len()/iterator()/set()/get()`(エントリ数メタを返す/更新する/メタから取得する)
|
||||
- ModuleFunction: `ArrayBox.len/0` / `MapBox.len/0`(メタのサイズを返す)— 他はタグ付き Fail‑Fast
|
||||
- Global/Extern: `env.console.{log|warn|error}`(数値引数のみ印字)
|
||||
- Others are Fail‑Fast(安定文言を出力)
|
||||
|
||||
See also: docs/development/architecture/collection_semantics.md(Array/Map のSSOT集約)
|
||||
|
||||
String helpers
|
||||
- Core route(Gate‑C/Core)での最小サポート(Method):
|
||||
- `String.size/0` — 文字列長(バイト)を返す
|
||||
- `String.indexOf/1` — 最初の一致位置、なければ -1
|
||||
- `String.lastIndexOf/1` — 最後の一致位置、なければ -1
|
||||
- `String.substring/2` — 半開区間 [start, end) を返す
|
||||
- インデックス規約(bytes ベース):
|
||||
- start/end は範囲 [0, size] にクランプされる(負の値は 0、size 超は size)。
|
||||
- start > end の場合は空文字(size==0)。
|
||||
- インデックスは UTF‑8 のバイト境界(コードポイント境界ではない)。
|
||||
- ModuleFunction:
|
||||
- `StringHelpers.to_i64/1` — interpreter に inline 実装あり(数値文字列のみを許容)。
|
||||
数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。
|
||||
|
||||
Core dispatcher canaries(直行ルート)
|
||||
- `profiles/quick/core/canary_core_dispatcher_*` は Gate‑C(Core) 直行へ移行済み。
|
||||
一部(大きな値や plugin‑enabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、
|
||||
rc はフォールバックとして扱う(TTL; 収束後に rc 検証に戻す)。
|
||||
|
||||
Aliases
|
||||
- Keep existing logical module names in `hako.toml` and introduce aliases to
|
||||
new paths when transitioning.
|
||||
16
lang/src/vm/boxes/README.md
Normal file
16
lang/src/vm/boxes/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# VM Boxes — Shared Helpers (Guard)
|
||||
|
||||
Responsibility
|
||||
- Pure helpers used by engines (op handlers, scanners, compare, JSON frag/cursor).
|
||||
|
||||
Allowed
|
||||
- Import from `lang/src/shared/*` and other boxes/*.
|
||||
|
||||
Forbidden
|
||||
- Engine orchestration (no main run loop, no dispatch)
|
||||
- Direct I/O or plugin/ABI calls (engines should own those)
|
||||
|
||||
Notes
|
||||
- Mini‑VM's minimal executor currently lives here (`mir_vm_min.hako`). It may
|
||||
move under `engines/mini/` later; keep it box‑pure (no I/O) until then.
|
||||
|
||||
123
lang/src/vm/boxes/arithmetic.hako
Normal file
123
lang/src/vm/boxes/arithmetic.hako
Normal file
@ -0,0 +1,123 @@
|
||||
// arithmetic.hako — ArithmeticBox
|
||||
// Responsibility: safe decimal Add/Sub/Mul helpers and simple i64 adapters.
|
||||
// Non-responsibility: VM execution, JSON parsing, compare semantics.
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box ArithmeticBox {
|
||||
// Internal helpers operate on decimal strings to avoid overflow.
|
||||
_to_dec_str(x) {
|
||||
local s = "" + x
|
||||
local i = 0
|
||||
loop(i < s.size()) { local ch = s.substring(i,i+1) if ch=="+" || ch==" " { i=i+1 } else { break } }
|
||||
s = s.substring(i, s.size())
|
||||
i = 0
|
||||
loop(i < s.size() && s.substring(i,i+1)=="0") { i = i + 1 }
|
||||
if i >= s.size() { return "0" }
|
||||
return s.substring(i, s.size())
|
||||
}
|
||||
|
||||
_cmp_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
if sa.size() < sb.size() { return -1 }
|
||||
if sa.size() > sb.size() { return 1 }
|
||||
local i = 0
|
||||
loop(i < sa.size()) {
|
||||
local ca = sa.substring(i,i+1)
|
||||
local cb = sb.substring(i,i+1)
|
||||
if ca != cb { if ca < cb { return -1 } else { return 1 } }
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
_add_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
local i = sa.size() - 1
|
||||
local j = sb.size() - 1
|
||||
local carry = 0
|
||||
local out = new ArrayBox()
|
||||
loop(i >= 0 || j >= 0 || carry > 0) {
|
||||
local da = 0
|
||||
local db = 0
|
||||
if i >= 0 { da = ("0123456789").indexOf(sa.substring(i,i+1)) i = i - 1 }
|
||||
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j,j+1)) j = j - 1 }
|
||||
local s = da + db + carry
|
||||
carry = s / 10
|
||||
local d = s % 10
|
||||
out.push(("0123456789").substring(d, d+1))
|
||||
}
|
||||
local k = out.size()
|
||||
local res = ""
|
||||
loop(k > 0) { k = k - 1 res = res + (""+out.get(k)) }
|
||||
return res
|
||||
}
|
||||
|
||||
_sub_dec(a, b) {
|
||||
// Supports negative result: if a<b, return "-" + (b-a)
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
local c = me._cmp_dec(sa, sb)
|
||||
if c == 0 { return "0" }
|
||||
if c < 0 { return "-" + me._sub_dec(sb, sa) }
|
||||
local i = sa.size() - 1
|
||||
local j = sb.size() - 1
|
||||
local borrow = 0
|
||||
local out = new ArrayBox()
|
||||
loop(i >= 0) {
|
||||
local da = ("0123456789").indexOf(sa.substring(i, i+1)) - borrow
|
||||
local db = 0
|
||||
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j, j+1)) j = j - 1 }
|
||||
if da < db { da = da + 10 borrow = 1 } else { borrow = 0 }
|
||||
local d = da - db
|
||||
out.push(("0123456789").substring(d, d+1))
|
||||
i = i - 1
|
||||
}
|
||||
local k = out.size() - 1
|
||||
loop(true) { if k > 0 && out.get(k) == "0" { k = k - 1 } else { break } }
|
||||
local res = ""
|
||||
loop(k >= 0) { res = res + (""+out.get(k)) k = k - 1 }
|
||||
return res
|
||||
}
|
||||
|
||||
_mul_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
if sa == "0" || sb == "0" { return "0" }
|
||||
local na = sa.size()
|
||||
local nb = sb.size()
|
||||
local res = new ArrayBox()
|
||||
local t = 0
|
||||
loop(t < na+nb) { res.push(0) t = t + 1 }
|
||||
local ia = na - 1
|
||||
loop(ia >= 0) {
|
||||
local da = ("0123456789").indexOf(sa.substring(ia, ia+1))
|
||||
local carry = 0
|
||||
local ib = nb - 1
|
||||
loop(ib >= 0) {
|
||||
local db = ("0123456789").indexOf(sb.substring(ib, ib+1))
|
||||
local idx = ia + ib + 1
|
||||
local sum = res.get(idx) + da * db + carry
|
||||
res.set(idx, sum % 10)
|
||||
carry = sum / 10
|
||||
ib = ib - 1
|
||||
}
|
||||
res.set(ia, res.get(ia) + carry)
|
||||
ia = ia - 1
|
||||
}
|
||||
local k = 0
|
||||
loop(k < res.size() && res.get(k) == 0) { k = k + 1 }
|
||||
local out = ""
|
||||
loop(k < res.size()) { out = out + ("0123456789").substring(res.get(k), res.get(k)+1) k = k + 1 }
|
||||
if out == "" { return "0" } else { return out }
|
||||
}
|
||||
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
|
||||
// Public adapters: operate on i64-likes and return i64-likes.
|
||||
add_i64(a, b) { return me._str_to_int(me._add_dec(a, b)) }
|
||||
sub_i64(a, b) { return me._str_to_int(me._sub_dec(a, b)) }
|
||||
mul_i64(a, b) { return me._str_to_int(me._mul_dec(a, b)) }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user