core: add Core Direct string canaries (substring/charAt/replace); Stage‑B: alias table (ENV) support with BundleResolver; docs: Exit Code Policy tag→rc rules and checklist updates.
This commit is contained in:
@ -7,6 +7,62 @@ static box BundleResolver {
|
||||
// - --require-mod Name must appear in named bundles ([bundle/missing] Name)
|
||||
// - Merge order: bundle-src* → bundle-mod* (in given order)
|
||||
resolve(bundle_srcs, bundle_names, bundle_mod_srcs, require_mods) {
|
||||
// Alias table via env: HAKO_BUNDLE_ALIAS_TABLE / NYASH_BUNDLE_ALIAS_TABLE
|
||||
// Format: entries separated by '|||', each entry as 'Name:code'
|
||||
local table = env.get("HAKO_BUNDLE_ALIAS_TABLE")
|
||||
if table == null || table == "" { table = env.get("NYASH_BUNDLE_ALIAS_TABLE") }
|
||||
if table != null && table != "" {
|
||||
local i = 0
|
||||
loop(i < table.length()) {
|
||||
// find next delimiter or end
|
||||
local j = table.indexOf("|||", i)
|
||||
local seg = ""
|
||||
if j >= 0 { seg = table.substring(i, j) } else { seg = table.substring(i, table.length()) }
|
||||
if seg != "" {
|
||||
local pos = -1
|
||||
local k = 0
|
||||
loop(k < seg.length()) { if seg.substring(k,k+1) == ":" { pos = k break } k = k + 1 }
|
||||
if pos >= 0 {
|
||||
local name = seg.substring(0, pos)
|
||||
local code = seg.substring(pos+1, seg.length())
|
||||
if name != "" && code != "" {
|
||||
if bundle_names == null { bundle_names = new ArrayBox() }
|
||||
if bundle_mod_srcs == null { bundle_mod_srcs = new ArrayBox() }
|
||||
bundle_names.push(name)
|
||||
bundle_mod_srcs.push(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
if j < 0 { break }
|
||||
i = j + 3
|
||||
}
|
||||
}
|
||||
// Env alias injection (TTL, dev-only): HAKO_BUNDLE_ALIAS_<Name> / NYASH_BUNDLE_ALIAS_<Name>
|
||||
// If a required module is not provided via --bundle-mod, but an env alias
|
||||
// supplies its code, synthesize a named bundle entry before checks/merge.
|
||||
if require_mods != null {
|
||||
local i0 = 0; local rn0 = require_mods.length()
|
||||
loop(i0 < rn0) {
|
||||
local need = "" + require_mods.get(i0)
|
||||
local present = 0
|
||||
if bundle_names != null {
|
||||
local j0 = 0; local bn0 = bundle_names.length()
|
||||
loop(j0 < bn0) { if ("" + bundle_names.get(j0)) == need { present = 1 break } j0 = j0 + 1 }
|
||||
}
|
||||
if present == 0 {
|
||||
local alias_key = "HAKO_BUNDLE_ALIAS_" + need
|
||||
local code = env.get(alias_key)
|
||||
if code == null || code == "" { code = env.get("NYASH_BUNDLE_ALIAS_" + need) }
|
||||
if code != null && code != "" {
|
||||
if bundle_names == null { bundle_names = new ArrayBox() }
|
||||
if bundle_mod_srcs == null { bundle_mod_srcs = new ArrayBox() }
|
||||
bundle_names.push(need)
|
||||
bundle_mod_srcs.push("" + code)
|
||||
}
|
||||
}
|
||||
i0 = i0 + 1
|
||||
}
|
||||
}
|
||||
// Fail on duplicate names
|
||||
if bundle_names != null && bundle_names.length() > 1 {
|
||||
local i = 0; local n = bundle_names.length()
|
||||
@ -50,4 +106,3 @@ static box BundleResolver {
|
||||
return merged
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Stage-B compiler entry — ParserBox → FlowEntry emit-only
|
||||
|
||||
using sh_core as StringHelpers // Required: ParserStringUtilsBox depends on this (using chain unresolved)
|
||||
include "lang/src/compiler/entry/bundle_resolver.hako"
|
||||
using lang.compiler.parser.box as ParserBox
|
||||
|
||||
// Note: Runner resolves entry as Main.main by default.
|
||||
@ -148,25 +149,7 @@ static box Main {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
// Fail‑Fast: required modules must be provided via --bundle-mod
|
||||
if require_mods.length() > 0 {
|
||||
local idx = 0
|
||||
local rn = require_mods.length()
|
||||
loop(idx < rn) {
|
||||
local need = "" + require_mods.get(idx)
|
||||
local found = 0
|
||||
{
|
||||
local j = 0
|
||||
local bn = bundle_names.length()
|
||||
loop(j < bn) { if ("" + bundle_names.get(j)) == need { found = 1 break } j = j + 1 }
|
||||
}
|
||||
if found == 0 {
|
||||
print("[bundle/missing] " + need)
|
||||
return 1
|
||||
}
|
||||
idx = idx + 1
|
||||
}
|
||||
}
|
||||
// Required modules are validated in BundleResolver.resolve (includes env alias injection)
|
||||
|
||||
// 4.6) Fail‑Fast on duplicate named bundles to avoid ambiguity
|
||||
// Policy: duplicate module names are not allowed. Emit a stable diagnostic tag and exit.
|
||||
@ -188,7 +171,6 @@ static box Main {
|
||||
}
|
||||
|
||||
if bundles.length() > 0 || bundle_srcs.length() > 0 {
|
||||
include "lang/src/compiler/entry/bundle_resolver.hako"
|
||||
local merged_prefix = BundleResolver.resolve(bundles, bundle_names, bundle_srcs, require_mods)
|
||||
if merged_prefix == null { return 1 }
|
||||
body_src = merged_prefix + body_src
|
||||
|
||||
@ -45,6 +45,19 @@ Exit Code Policy
|
||||
- Gate‑C(Core): numeric return is mapped to process exit code。タグ付きの失敗時は安定メッセージを出し、可能な限り非0で終了。
|
||||
- VM backend(Rust Interpreter): 戻り値は標準出力に出す。プロセスの終了コードは戻り値と一致しない場合があるため、スモークは安定タグや標準出力の数値で検証する(rcは参考)。
|
||||
- 推奨: CIやスクリプトでは Gate‑C(Core) を優先し rc を厳密化。開発時の対話検証は VM ルートで標準出力を検証。
|
||||
|
||||
Tag→RC(Core Direct)
|
||||
- Core Direct(`HAKO_CORE_DIRECT=1`)では、数値行が見つからない場合は rc≠0 を返す(Fail‑Fast)。
|
||||
- 代表タグ(例)
|
||||
- `[core/string/bounds]` → rc=1
|
||||
- `[core/array/oob_set]` → rc=1
|
||||
- `[core/mir_call/method_unsupported]` → rc=1
|
||||
|
||||
Core Direct Toggle
|
||||
- `HAKO_CORE_DIRECT=1`(互換: `NYASH_CORE_DIRECT`)で、Gate‑C(Core) の JSON 実行を "Core Dispatcher 直行" 子経路に切り替える。
|
||||
- 形: 一時Hakoスクリプトに `include "lang/src/vm/core/dispatcher.hako"` を埋め込み、`NyVmDispatcher.run(json)` を実行。
|
||||
- rc: 最後の数値行を rc にマップ。数値がない場合(タグ等)は rc≠0 とする(Fail‑Fast)。
|
||||
- 用途: Core の診断タグや rc を CI で直接検証したい時に使用。
|
||||
- Runner Core toggle: `HAKO_NYVM_CORE=1` (or `NYASH_NYVM_CORE=1`) selects the
|
||||
Core bridge for the nyvm wrapper path.
|
||||
- Gate‑C Core route: set `NYASH_GATE_C_CORE=1` (or `HAKO_GATE_C_CORE=1`) to
|
||||
|
||||
@ -401,7 +401,7 @@ static box NyVmOpMirCall {
|
||||
if method == "keys" {
|
||||
local dst = me._read_dst(inst_json, "map keys")
|
||||
if dst == null { return -1 }
|
||||
// Build keys by scanning mem for receiver-specific entry slots
|
||||
// Build keys by scanning mem for receiver-specific entry slots (non-null values only)
|
||||
local keys_arr = new ArrayBox()
|
||||
local prefix = me._map_entry_slot(recv_id, "")
|
||||
// Iterate memory keys (MapBox.keys())
|
||||
@ -416,8 +416,11 @@ static box NyVmOpMirCall {
|
||||
if k.substring(0, prefix.length()) == prefix { ok = 1 }
|
||||
}
|
||||
if ok == 1 {
|
||||
local tail = k.substring(prefix.length(), k.length())
|
||||
keys_arr.push(tail)
|
||||
local v = mem.get(k)
|
||||
if v != null {
|
||||
local tail = k.substring(prefix.length(), k.length())
|
||||
keys_arr.push(tail)
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
@ -473,7 +476,20 @@ static box NyVmOpMirCall {
|
||||
return 0
|
||||
}
|
||||
if method == "clear" {
|
||||
// clear(): set length to 0; entries are logically cleared(TTL: metadata‑only)
|
||||
// clear(): set length to 0 and remove all entries for this map from mem
|
||||
local prefix = me._map_entry_slot(recv_id, "")
|
||||
local all_keys = mem.keys()
|
||||
local i = 0
|
||||
local n = all_keys.length()
|
||||
loop(i < n) {
|
||||
local k = "" + all_keys.get(i)
|
||||
local ok = 0
|
||||
if k.length() >= prefix.length() {
|
||||
if k.substring(0, prefix.length()) == prefix { ok = 1 }
|
||||
}
|
||||
if ok == 1 { mem.set(k, null) }
|
||||
i = i + 1
|
||||
}
|
||||
me._map_len_set(state, recv_id, 0)
|
||||
local dst_opt2 = me._read_optional_vid_field(inst_json, "dst")
|
||||
if dst_opt2 != null { NyVmState.set_reg(state, dst_opt2, void) }
|
||||
|
||||
Reference in New Issue
Block a user