Files
hakorune/lang/src/shared/common/string_helpers.hako
nyash-codex 4a48d6afa3 refactor(stage1): Phase 25.4-B準備 - Stage-1 CLI env処理改善
🎯 目的: Stage-1 CLI の env/トグル処理を整理・改善

 改善内容:
- stage1_cli.hako: 関数名修正・簡略化
  - パラメータ名を cli_args_raw に統一
  - __mir__.log マーカー整備(デバッグ用)
  - env処理のコメント改善

- string_helpers.hako: to_i64 改善
  - null/Void ガード追加
  - NYASH_TO_I64_DEBUG 対応
  - NYASH_TO_I64_FORCE_ZERO トグル準備

- tools/stage1_debug.sh: デバッグ改善
  - NYASH_TO_I64_DEBUG フラグ追加
  - NYASH_TO_I64_FORCE_ZERO フラグ追加
  - ログ観測の改善

- apps/tests/minimal_to_i64_void.hako: テストケース追加
  - Void値の to_i64 処理確認用

📋 Phase 25.4-B への準備:
- 次フェーズで Stage1CliConfigBox を導入予定
- env.get() を Config 箱に集約する基盤完成
- 既存動作は維持(Fail-Fast + テスト緑キープ)

🎯 効果:
- デバッグ観測性向上
- Void/null 処理の安全性向上
- 将来の Config 箱化への準備完了

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 09:02:02 +09:00

220 lines
6.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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) {
// Optional debug hook: observe incoming values/boxes.
// Enable with NYASH_TO_I64_DEBUG=1 when diagnosing numeric coercions.
if env.get("NYASH_TO_I64_DEBUG") == "1" {
__mir__.log("[string_helpers/to_i64] x", x)
}
// Fail-close for unexpected boxes (e.g., Void) when explicitly requested.
// This avoids String + Void type errors during Stage-1 CLI bring-up.
if env.get("NYASH_TO_I64_FORCE_ZERO") == "1" { return 0 }
if x == null { return 0 }
local s = "" + x
local i = 0
local neg = 0
if s.substring(0,1) == "-" { neg = 1 i = 1 }
local n = s.length()
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.length()
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.length()
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
}
// ------------------------------------
// NEW: 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.length()
local m = pat.length()
{
// Dev-only trace for StageB / parser調査用
local dbg = env.get("HAKO_STAGEB_DEBUG")
if dbg != null && ("" + dbg) == "1" {
print("[string_helpers/starts_with] src=\"" + src + "\"")
print("[string_helpers/starts_with] i=" + ("" + i) + " m=" + ("" + m) + " n=" + ("" + n))
}
}
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) {
{
local dbg = env.get("HAKO_STAGEB_DEBUG")
if dbg != null && ("" + dbg) == "1" {
print("[string_helpers/starts_with_kw] src=\"" + src + "\" i=" + ("" + i) + " kw=\"" + kw + "\"")
}
}
// 同一箱内ヘルパーとして、receiver 経由ではなく
// 明示的な箱名付きの静的呼び出しにする(引数順のずれ防止)。
if StringHelpers.starts_with(src, i, kw) == 0 { return 0 }
local n = src.length()
local j = i + kw.length()
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.length()
local m = pat.length()
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.length()
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.length()
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.length()
local m = pat.length()
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
}
// Split string by newline into ArrayBox (without relying on StringBox.split)
split_lines(s) {
local arr = new ArrayBox()
if s == null { return arr }
local n = s.length()
local last = 0
local i = 0
loop (i < n) {
local ch = s.substring(i, i+1)
if ch == "\n" {
arr.push(s.substring(last, i))
last = i + 1
}
i = i + 1
}
// push tail
if last <= n { arr.push(s.substring(last)) }
return arr
}
}