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:
nyash-codex
2025-10-31 20:45:46 +09:00
parent dbc285f2b1
commit e5f697eb22
244 changed files with 16915 additions and 47 deletions

1
lang/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/bin/

37
lang/README.md Normal file
View 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.
- FailFast: 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`
NonGoals
- 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.

View 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
View 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.
- Readonly 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 (readonly): `hako_gc_stats`, `hako_gc_roots_snapshot`
- Console: `hako_console_log/warn/error` (void sideeffect; 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).

View 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 (FailFast):
- `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.

View File

@ -0,0 +1,39 @@
// hako_aot.h — AOT CABI (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 (threadlocal): "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 (libcbacked). 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; nonzero 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; nonzero 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

View 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

View File

@ -0,0 +1,145 @@
// hako_hostbridge.h — HostBridge CABI 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 (UTF8). 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
View File

@ -0,0 +1,207 @@
// hako_aot.c — Small standalone AOT CABI
// 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 (threadlocal 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;
}

View 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
View 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/ が共存(ミラー配置)。
- 参照更新は小バッチで実施し、スモークで形状と出力を固定。

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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 = "="

View File

@ -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 Stage1 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

View File

@ -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 JSONcall/boxcall/newbox。最小入力: Stage1 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_cexterns を 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 }
}

View File

@ -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 MiniVM exec
lower_stage1_to_mir_v1_compat(ast_json, prefer_cfg) {
local j1 = PipelineV2.lower_stage1_to_mir_v1(ast_json, prefer_cfg)

View File

@ -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 } }

View File

@ -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)
}
}

View File

@ -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) {

View 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
}
}

View 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"] }
}

View 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 へ委譲予定)
FailFast
- 未実装/未対応は `UNSUPPORTED: <op>` を短文で出力して負値を返す(将来は統一エラーへ)。
将来拡張
- v1: compare/branch/phi、v2: call/externhako_* の C-ABI のみ)
- MIR→IR の対応は SSOT に集約し、Builder は小さな純関数にまとめる。

View 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){
// FailFast: 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 CABI/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 FailFast
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)
}
}

View 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。失敗時は入力パスを返すFailFastは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 }
// Phase1: 文字列正規化(安定化)
// いまは canonicalize は恒等将来はHostBridgeでキー順安定化
local canon = MirIoBox.normalize(src)
// Phase2: 安全な単一ブロック const/binop(+,-,*)/ret の畳み込み(最小実装)
// 備考: まずは main 関数を優先対象とし、成立時のみ最小 JSON に置換(今後は inplace 置換へ段階移行)。
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
}
}

View 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}] } ] } ] }"
}
}

View File

@ -0,0 +1,9 @@
// LLVMEmitBox — オブジェクト出力当面は委譲予定・MVPはFailFast stub
static box LLVMEmitBox {
write_object(mod_handle, path){
// まだスクリプト内で IR→obj を完結させない方針。委譲前提のためFailFast。
print("UNSUPPORTED: write_object (delegate to AotBox/libhako_aot)");
return -1
}
}

View 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
}
}

View File

@ -0,0 +1,21 @@
// LLVMModuleBox — IR 構築の起点MVPは形のみ
static box LLVMModuleBox {
new(name, triple, dl){
// Gate: optin のみ
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
}
}

View 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 + "*" }
}

View 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 0v0代表
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)
}
}

View File

@ -0,0 +1,49 @@
// v0_const_binop.hako — LLVM Script Builder v0const/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
}
}

View 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",
]

View File

@ -0,0 +1,27 @@
// ExternsSSOTEmitter — dev-only SSOT generator (Phase20.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";
}
}

View File

@ -0,0 +1,114 @@
// min_emitter.hako — Minimal MIR(JSON v0) emitter (const/ret/binop)
// Dev/optin helper for P1: Hakoside 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 } }

View 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"

View 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 } }

View 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
View 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; FailFast 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 emitexe.
- Later we may switch compile input behind a dedicated flag once stable.

28
lang/src/runner/README.md Normal file
View File

@ -0,0 +1,28 @@
# Runner Facade (ScriptFirst) — Phase 20.10
Responsibility
- Provide a thin, optin 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`
- Prehooks: validate entry, shape args, emit diagnostics (short tokens) on failure.
- Posthooks: 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 CABI utilities (`hako_*`).
- FailFast: invalid entry/args → emit short diagnostics and return nonzero.
- BoxFirst: add adapters for boundary objects (args, env) instead of sprinkling conditions.
Short Diagnostics & Observability
- Preinvoke emits stable oneliners 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)`.
Devonly toggle (TTL: Phase 20.10 bringup)
- `HAKO_SCRIPT_RUNNER_FORCE_FAIL=1` forces the preinvoke 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).

View 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.26C scaffold. Optin 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}`
- FailFast on ambiguity; no silent fallbacks.

View File

@ -0,0 +1,34 @@
// RunnerPlanBuilder (JSON Plan) — Phase 20.26C
// 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
}
}

View File

@ -0,0 +1,15 @@
// GateC 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)
}
}

View File

@ -0,0 +1,7 @@
[module]
name = "selfhost.runner"
version = "1.0.0"
[exports]
Runner = "runner_facade.hako"

View 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;
}
}

View 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;
}
}

View 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), besteffort integer
roots_snapshot() {
return call("env.gc.roots_snapshot/0")
}
// Request collection (noop until host supports it)
collect() {
// Host may ignore; keep FailFast if extern missing
call("env.gc.collect/0")
}
// Optional lifecycle hooks (noop 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
}
}

View 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 }
}

View 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) }
}

View 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"

View File

@ -0,0 +1,153 @@
// JsonShapeToMap — minimal, optin 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 })
}
}

View 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"
}
}

View 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 (Phase20.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\":{}}";
}
}

View 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"

View 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"] }
}

View File

@ -0,0 +1,30 @@
SelfHost MIR Builder (Scaffold) — Phase20.12b
Purpose
- Prepare a minimal, optin skeleton to generate MIR(JSON v0) in Hakorune (selfhost) alongside the current Rust generator.
- Keep default behavior unchanged (Rust MIR remains the source of truth). This module is emitonly in early phases.
Scope (this scaffold)
- Files: builder.hako, phi.hako, verify.hako, LAYER_GUARD.hako
- Behavior: noop/identity placeholders with clearly defined interfaces and gates.
- Docs: specs/mir-json-v0.md describes the minimal JSON v0 shape targeted by this builder.
NonGoals (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 (emitonly; v0 returns input path)
- SelfhostMirVerify.verify(json_path) -> bool/int (0=ok; v0 always ok)
- SelfhostPhiBox helpers (shape only; no logic yet)
Gates (optin)
- NYASH_USE_NY_COMPILER=1 → future: emitonly 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.

View File

@ -0,0 +1,18 @@
// builder.hako — Selfhost MIR(JSON v0) Builder (Scaffold)
// Phase20.12b: emitonly 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
}
}

View File

@ -0,0 +1,17 @@
// phi.hako — PHI helpers (Scaffold)
// Phase20.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
}
}

View File

@ -0,0 +1,10 @@
// verify.hako — Selfhost MIR verifier (Scaffold)
// Phase20.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)
}
}

View 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.

View 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)
}
}

View 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
}
}

View 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 } }

View 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))
}
}

View 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
}
}

View 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 ""
}
}

View File

@ -0,0 +1,39 @@
// modules_inspect_box.hako — ModulesInspectBox
// Responsibility: Helpers for Dir-as-NS module inspection from Nyash code.
// NonIO, 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 } }

View 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
}
}

View 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
}
}

View 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"

View 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)
}
}

View 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)
}
}

View 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
}
}

View 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: identityhost bridge未接続の間は常にno-op
// 期待動作: 文字列正規化は将来のExternに委譲。現状はそのまま返す。
return "" + json
}
}

View 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 } }

View 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\"}"
_ => "{}"
}
}
}

View 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
}
}

View 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 } }

View 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
}
}

View 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 } }

View 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 } }

View 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`.
}

View 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)
}
}

View 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 構造の最小組み立て。

View 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)
}
}

View 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
}
}

View 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))
}
}

View 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
// FailFast:
// - 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)
}
}

View 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
}
}

View 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"] }
}

View 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
NonGoals
- No filesystem/ABI side-effects beyond read-only inspection.
TTL
- Diagnostics/formatting migrates from Rust boundary to this front EXE when stable.
Notes (AOT / SingleEXE)
- 将来的に単一exeに内蔵AOTされ、ENV反映→通常ロード継続を同一プロセスで行います。
- 開発時は tools/front_exe/loader_front.sh をゲートONで差し替え可能です。

View 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"] }
}

View 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 / SingleEXE)
- 本モジュールは将来、単一の 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 nonzero (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)
NonGoals
- 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.

View 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"] }
}

View 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 / SingleEXE)
- 将来的に単一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
NonGoals
- No execution, no state, no ABI interaction.
TTL
- Runner-side formatting will migrate here after stabilization.

15
lang/src/vm/DEPRECATED.md Normal file
View File

@ -0,0 +1,15 @@
# DEPRECATED: selfhost/vm (MiniVM sandbox)
This directory hosts the original MiniVM used for early selfhosting.
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.*`.
- MiniVM specific smokes remain rc-only and opt-in.
Removal trigger
- When Hakorune VM reaches feature parity and quick/integration remain green for one sprint, MiniVM will be retired.

View File

@ -0,0 +1,6 @@
// LAYER_GUARD — VM Layer Guard (Phase 20.8)
// Policy:
// - lang/src 優先。lang/src 配下からの `using "selfhost/..."` 直参照は禁止。
// - 参照は lang/src の等価箱(ミラー)へ統一し、段階撤退を進める。
// - MiniVM の新規機能追加は慎重に(既定は凍結・スモークで仕様固定)。
static box MiniVmLayerGuard { main(args) { return 0 } }

105
lang/src/vm/README.md Normal file
View 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.)
- MiniVM minimal executor lives as boxes (e.g., `boxes/mir_vm_min.hako`)
Target (post20.12b, gradual)
- `engines/hakorune/` — mainline nyvm engine
- `engines/mini/` — MiniVM 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 engineagnostic execution primitives and should not import
enginespecific modules. During migration, temporary adapters may exist.
BridgeB (Ny/Core 直行)
- Wrapper 経路では `include "lang/src/vm/core/dispatcher.hako"` で Core Dispatcher を取り込み、
`NyVmDispatcher.run(json)` を直接呼び出す。`using` は名前解決のみで実体は登録されないため、
Core を呼ぶ目的では `include` を用いること。
GateC(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`
- GateC(Core, json→Core 直行) canaries: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh`既定OFF
- GateC(Core) array sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.sh`push→set→get をログで検証)
- GateC(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`
- GateC 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.
- GateC 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で終了FailFast
Deprecations
- `NYASH_GATE_C_DIRECT` は移行中の互換トグルTTLだよ。将来は GateC(Core)
直行(`HAKO_GATE_C_CORE=1`)に統一予定。新しい導線では Core の実行仕様(数値=rc,
安定化した診断タグ)が適用されるよ。
- 互換トグルを使うと起動時に警告が出るよ(`HAKO_GATE_C_DIRECT_SILENCE=1` で抑止可)。
Diagnostics (stable tags)
- 本フェーズでは、GateC(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`
- GateC Direct では、リーダー/検証レイヤの診断をそのまま用いる(例: `unsupported callee type (expected Extern): ModuleFunction`)。
Exit code differences
- Core: 数値=rcOS仕様により 0255 に丸められる。例: 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`(メタのサイズを返す)— 他はタグ付き FailFast
- Global/Extern: `env.console.{log|warn|error}`(数値引数のみ印字)
- Others are FailFast安定文言を出力
See also: docs/development/architecture/collection_semantics.mdArray/Map のSSOT集約
String helpers
- Core routeGateC/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
- インデックスは UTF8 のバイト境界(コードポイント境界ではない)。
- ModuleFunction:
- `StringHelpers.to_i64/1` — interpreter に inline 実装あり(数値文字列のみを許容)。
数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。
Core dispatcher canaries直行ルート
- `profiles/quick/core/canary_core_dispatcher_*` は GateC(Core) 直行へ移行済み。
一部(大きな値や pluginenabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、
rc はフォールバックとして扱うTTL; 収束後に rc 検証に戻す)。
Aliases
- Keep existing logical module names in `hako.toml` and introduce aliases to
new paths when transitioning.

View 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
- MiniVM's minimal executor currently lives here (`mir_vm_min.hako`). It may
move under `engines/mini/` later; keep it boxpure (no I/O) until then.

View 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