json(vm): fix birth dispatch; unify constructor naming (Box.birth/N); JsonNode factories return JsonNodeInstance; quick: enable heavy JSON with probe; builder: NYASH_BUILDER_DEBUG_LIMIT guard; json_query_min(core) harness; docs/tasks updated

This commit is contained in:
nyash-codex
2025-09-27 08:45:25 +09:00
parent fcf8042b06
commit cb236b7f5a
263 changed files with 12990 additions and 272 deletions

View File

@ -9,11 +9,62 @@ using "apps/lib/json_native/utils/escape.nyash" as EscapeUtils
// 🌟 JSON値を表現するBoxEverything is Box原則
static box JsonNode {
// --- Internal normalization helper (dev-safe) ---
// Convert arbitrary values (MapBox/ArrayBox/primitives) into JsonNode or null.
// Heuristics only; keeps behavior stable without requiring the parser here.
normalize_any_(x) {
if x == null { return null }
// Already a JsonNode (stringify + get_kind expected)
if x.stringify != null and x.get_kind != null {
return x
}
// MapBox → object JsonNode (key-wise wrap)
if x.keys != null and x.get != null and x.set != null {
local out = this.create_object()
local ks = x.keys()
local i = 0
loop(i < ks.length()) {
local k = ks.get(i)
local v = x.get(k)
out.object_set(k, this.normalize_any_(v))
i = i + 1
}
return out
}
// ArrayBox → array JsonNode (index-wise wrap)
if x.length != null and x.get != null and x.push != null {
local out = this.create_array()
local i = 0
local n = x.length()
loop(i < n) {
out.array_push(this.normalize_any_(x.get(i)))
i = i + 1
}
return out
}
// Primitive-ish fallback: derive from textual form
if x.toString != null {
local s = x.toString()
// Fast common literals
if s == "null" { return null }
if s == "true" { return this.create_bool(true) }
if s == "false" { return this.create_bool(false) }
// Integer detection
if StringUtils.is_integer(s) {
return this.create_int(StringUtils.parse_integer(s))
}
// As a last resort, wrap as string
return this.create_string(s)
}
// Unknown safest is null
return null
}
// ===== ファクトリーメソッド =====
// null値を作成
create_null() {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "null"
node.value = null
return node
@ -21,7 +72,7 @@ static box JsonNode {
// bool値を作成
create_bool(b) {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "bool"
node.value = b
return node
@ -29,7 +80,7 @@ static box JsonNode {
// int値を作成
create_int(i) {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "int"
node.value = i
return node
@ -37,7 +88,7 @@ static box JsonNode {
// float値を作成値は文字列表現を保持
create_float(fstr) {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "float"
node.value = fstr // Phase 1: 文字列として保持演算不要、stringify重視
return node
@ -45,7 +96,7 @@ static box JsonNode {
// string値を作成
create_string(s) {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "string"
node.value = s
return node
@ -53,7 +104,7 @@ static box JsonNode {
// array値を作成
create_array() {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "array"
node.value = new ArrayBox()
return node
@ -61,7 +112,7 @@ static box JsonNode {
// object値を作成
create_object() {
local node = new JsonNode()
local node = new JsonNodeInstance()
node.kind = "object"
node.value = new MapBox()
return node
@ -73,7 +124,7 @@ static box JsonNode {
parse(json_text) {
// TODO: 本格的なレクサー・パーサーは後で実装
// まずは超シンプルな実装で動作確認
// 空白をトリム(美しいモジュラー設計)
local text = StringUtils.trim(json_text)
@ -95,6 +146,10 @@ static box JsonNode {
local num = StringUtils.parse_integer(text)
return this.create_int(num)
}
// 浮動小数点(簡易検出: 小数点/指数記号を含む)
if StringUtils.contains(text, ".") or StringUtils.contains(text, "e") or StringUtils.contains(text, "E") {
return this.create_float(text)
}
// 文字列(エスケープ処理対応)
if StringUtils.starts_with(text, "\"") and StringUtils.ends_with(text, "\"") {
@ -111,8 +166,41 @@ static box JsonNode {
// オブジェクト(超シンプル版)
if StringUtils.starts_with(text, "{") and StringUtils.ends_with(text, "}") {
// 単一キーの素朴なハンドリング: {"k":<num|string|bool|null>}
local object_node = this.create_object()
// TODO: キー・バリューのパース処理は後で実装
local inner = text.substring(1, text.length() - 1)
inner = StringUtils.trim(inner)
if inner.length() == 0 {
return object_node
}
// 最低限の分割(コロン一箇所想定): indexOf があれば活用
local sep = -1
if inner.indexOf != null { sep = inner.indexOf(":") }
if sep == -1 {
return object_node
}
local kraw = StringUtils.trim(inner.substring(0, sep))
local vraw = StringUtils.trim(inner.substring(sep + 1, inner.length()))
// キーはダブルクォート必須(簡易)
if StringUtils.starts_with(kraw, "\"") and StringUtils.ends_with(kraw, "\"") {
local key = EscapeUtils.unquote_string(kraw)
// 値は既存の簡易規則に委譲
local val
if vraw == "null" { val = this.create_null() } else {
if vraw == "true" { val = this.create_bool(true) } else {
if vraw == "false" { val = this.create_bool(false) } else {
if StringUtils.is_integer(vraw) { val = this.create_int(StringUtils.parse_integer(vraw)) } else {
if StringUtils.contains(vraw, ".") or StringUtils.contains(vraw, "e") or StringUtils.contains(vraw, "E") { val = this.create_float(vraw) } else {
if StringUtils.starts_with(vraw, "\"") and StringUtils.ends_with(vraw, "\"") { val = this.create_string(EscapeUtils.unquote_string(vraw)) } else {
val = this.create_null()
}
}
}
}
}
}
object_node.object_set(key, val)
}
return object_node
}
@ -159,10 +247,14 @@ static box JsonNode {
return 0
}
// 配列の要素を取得
// 配列の要素を取得(欠損時は null を返す)
array_get(index) {
if me.kind == "array" {
return me.value.get(index)
if index >= 0 and index < me.value.length() {
return this.normalize_any_(me.value.get(index))
} else {
return null
}
}
return null
}
@ -174,10 +266,14 @@ static box JsonNode {
}
}
// オブジェクトのキーを取得
// オブジェクトのキーを取得(欠損時は null を返す)
object_get(key) {
if me.kind == "object" {
return me.value.get(key)
if me.value.has(key) {
return this.normalize_any_(me.value.get(key))
} else {
return null
}
}
return null
}
@ -212,7 +308,8 @@ static box JsonNode {
}
} else {
if me.kind == "int" {
return me.value.toString()
// Avoid relying on primitive method resolution; force string via concat
return "" + me.value
} else {
if me.kind == "float" {
// 数値の文字列表現をそのまま出力(引用符なし)
@ -221,38 +318,61 @@ static box JsonNode {
if me.kind == "string" {
return EscapeUtils.quote_string(me.value)
} else {
if me.kind == "array" {
local parts = new ArrayBox()
local i = 0
loop(i < me.value.length()) {
local elem = me.value.get(i)
parts.push(elem.stringify())
i = i + 1
}
return "[" + StringUtils.join(parts, ",") + "]"
} else {
if me.kind == "object" {
local parts = new ArrayBox()
local keys = me.value.keys()
local i = 0
loop(i < keys.length()) {
local key = keys.get(i)
local val = me.value.get(key)
local pair = EscapeUtils.quote_string(key) + ":" + val.stringify()
parts.push(pair)
i = i + 1
}
return "{" + StringUtils.join(parts, ",") + "}"
} else {
return "null"
}
}
if me.kind == "array" {
local parts = new ArrayBox()
local i = 0
loop(i < me.value.length()) {
local elem = me.value.get(i)
parts.push(elem.stringify())
i = i + 1
}
return "[" + StringUtils.join(parts, ",") + "]"
} else {
if me.kind == "object" {
// 安定出力のためキーを辞書順で整列
local keys = me.value.keys()
// 簡易バブルソート(要素数が小さい前提)
local n = keys.length()
local i = 0
loop(i < n) {
local j = 0
loop(j + 1 < n) {
local a = keys.get(j)
local b = keys.get(j + 1)
if a > b {
// swap
keys.set(j, b)
keys.set(j + 1, a)
}
j = j + 1
}
i = i + 1
}
local parts = new ArrayBox()
local k = 0
loop(k < keys.length()) {
local key = keys.get(k)
local val = me.value.get(key)
local pair = EscapeUtils.quote_string(key) + ":" + val.stringify()
parts.push(pair)
k = k + 1
}
return "{" + StringUtils.join(parts, ",") + "}"
} else {
return "null"
}
}
}
}
}
}
}
}
// 互換: toString は stringify と等価
toString() {
return me.stringify()
}
// ===== ヘルパーメソッド =====
// 美しいモジュラー設計により、重複コードを削除
@ -304,7 +424,8 @@ box JsonNodeInstance {
if me.value { return "true" } else { return "false" }
}
if me.kind == "int" {
return me.value.toString()
// Avoid relying on primitive method resolution; force string via concat
return "" + me.value
}
if me.kind == "float" {
return me.value
@ -323,21 +444,42 @@ box JsonNodeInstance {
return "[" + StringUtils.join(parts, ",") + "]"
}
if me.kind == "object" {
local parts = new ArrayBox()
// 安定出力のためキーを辞書順で整列
local keys = me.value.keys()
local n = keys.length()
local i = 0
loop(i < keys.length()) {
local key = keys.get(i)
loop(i < n) {
local j = 0
loop(j + 1 < n) {
local a = keys.get(j)
local b = keys.get(j + 1)
if a > b {
keys.set(j, b)
keys.set(j + 1, a)
}
j = j + 1
}
i = i + 1
}
local parts = new ArrayBox()
local k = 0
loop(k < keys.length()) {
local key = keys.get(k)
local val = me.value.get(key)
local pair = EscapeUtils.quote_string(key) + ":" + val.stringify()
parts.push(pair)
i = i + 1
k = k + 1
}
return "{" + StringUtils.join(parts, ",") + "}"
}
return "null"
}
// 互換: toString は stringify と等価
toString() {
return me.stringify()
}
// Instance helper: push element into array value
array_push(node) {
if me.kind == "array" {
@ -353,10 +495,26 @@ box JsonNodeInstance {
return 0
}
// Instance helper: object get
// Instance helper: array get欠損時は null を返す)
array_get(index) {
if me.kind == "array" {
if index >= 0 and index < me.value.length() {
return JsonNode.normalize_any_(me.value.get(index))
} else {
return null
}
}
return null
}
// Instance helper: object get欠損時は null を返す)
object_get(key) {
if me.kind == "object" {
return me.value.get(key)
if me.value.has(key) {
return JsonNode.normalize_any_(me.value.get(key))
} else {
return null
}
}
return null
}

View File

@ -24,6 +24,15 @@ box JsonScanner {
me.line = 1
me.column = 1
}
// Runtime-safe initializer (bypass constructor arg loss on some VM paths)
reset_text(input_text) {
me.text = input_text
me.position = 0
me.length = input_text.length()
me.line = 1
me.column = 1
}
// ===== 基本読み取りメソッド =====
@ -233,6 +242,10 @@ box JsonScanner {
// 整数部分
if me.current() == "0" {
me.advance()
// 先頭0の直後に数字が続くのは無効例: 01
if me.is_digit_char(me.current()) {
return null
}
} else {
if me.is_digit_char(me.current()) {
loop(not me.is_eof() and me.is_digit_char(me.current())) {
@ -312,7 +325,10 @@ box JsonScanner {
i = i + 1
}
} else {
// 通常のエスケープ
// 通常のエスケープ\" \\ \/ \b \f \n \r \t のみ)
if not (escaped == "\"" or escaped == "\\" or escaped == "/" or escaped == "b" or escaped == "f" or escaped == "n" or escaped == "r" or escaped == "t") {
return null
}
me.advance()
}
} else {

View File

@ -15,7 +15,8 @@ box JsonTokenizer {
birth(input_text) {
// Avoid static module wrapper to ensure constructor args are preserved on VM path
// (create_scanner(...) lost the argument under VM fallback in some cases)
me.scanner = new JsonScanner(input_text)
me.scanner = new JsonScanner("")
me.scanner.reset_text(input_text)
me.tokens = new ArrayBox()
me.errors = new ArrayBox()
}
@ -60,6 +61,14 @@ box JsonTokenizer {
return me.tokens
}
// Explicitly set input text (guards against constructor-arg loss)
set_input(input_text) {
if me.scanner == null {
me.scanner = new JsonScanner("")
}
me.scanner.reset_text(input_text)
}
// 次のトークンを1つ取得
next_token() {

View File

@ -2,7 +2,6 @@
// 責務: トークン列をJsonNodeに変換、構文エラー検出、ネスト構造処理
using "apps/lib/json_native/lexer/tokenizer.nyash" as JsonTokenizer
using "apps/lib/json_native/lexer/token.nyash" as JsonToken
using "apps/lib/json_native/lexer/token.nyash" as TokenType
using "apps/lib/json_native/core/node.nyash" as JsonNode
using "apps/lib/json_native/utils/string.nyash" as StringUtils
@ -35,6 +34,8 @@ box JsonParser {
// Step 1: 字句解析
local tokenizer = new JsonTokenizer(json_text)
// Guard: ensure scanner has the correct input even if constructor args are dropped
tokenizer.set_input(json_text)
me.tokens = tokenizer.tokenize()
// 字句解析エラーをチェック
@ -76,40 +77,33 @@ box JsonParser {
local token_type = token.get_type()
// リテラル値
// リテラル値・構造のディスパッチ(厳密一致)
if token_type == "NULL" {
me.advance()
return JsonNode.create_null()
} else {
if token_type == "TRUE" {
me.advance()
return JsonNode.create_bool(true)
} else {
if token_type == "FALSE" {
me.advance()
return JsonNode.create_bool(false)
} else {
if token_type == "NUMBER" {
return me.parse_number()
} else {
if token_type == "STRING" {
return me.parse_string()
} else {
if token_type == "LBRACE" {
return me.parse_object()
} else {
if token_type == "LBRACKET" {
return me.parse_array()
} else {
me.add_error("Expected JSON value, got: " + token_type)
return null
}
}
}
}
}
}
}
if token_type == "TRUE" {
me.advance()
return JsonNode.create_bool(true)
}
if token_type == "FALSE" {
me.advance()
return JsonNode.create_bool(false)
}
if token_type == "NUMBER" {
return me.parse_number()
}
if token_type == "STRING" {
return me.parse_string()
}
if token_type == "LBRACE" {
return me.parse_object()
}
if token_type == "LBRACKET" {
return me.parse_array()
}
me.add_error("Expected JSON value, got: " + token_type)
return null
}
// ===== 専用パーサーメソッド =====
@ -117,7 +111,7 @@ box JsonParser {
// 数値解析
parse_number() {
local token = me.current_token()
if token == null or token.get_type() != TokenType.NUMBER() {
if token == null or token.get_type() != "NUMBER" {
me.add_error("Expected number token")
return null
}
@ -142,7 +136,7 @@ box JsonParser {
// 文字列解析
parse_string() {
local token = me.current_token()
if token == null or token.get_type() != TokenType.STRING() {
if token == null or token.get_type() != "STRING" {
me.add_error("Expected string token")
return null
}
@ -156,7 +150,7 @@ box JsonParser {
// オブジェクト解析
parse_object() {
local start_token = me.current_token()
if start_token == null or start_token.get_type() != TokenType.LBRACE() {
if start_token == null or start_token.get_type() != "LBRACE" {
me.add_error("Expected '{' to start object")
return null
}
@ -173,7 +167,7 @@ box JsonParser {
loop(true) {
// キー解析
local key_token = me.current_token()
if key_token == null or key_token.get_type() != TokenType.STRING() {
if key_token == null or key_token.get_type() != "STRING" {
me.add_error("Expected string key in object")
return null
}
@ -181,7 +175,7 @@ box JsonParser {
me.advance()
// コロン
if not me.match_token(TokenType.COLON()) {
if not me.match_token("COLON") {
me.add_error("Expected ':' after object key")
return null
}
@ -196,11 +190,11 @@ box JsonParser {
object_node.object_set(key, value)
// 継続判定
if me.match_token(TokenType.COMMA()) {
if me.match_token("COMMA") {
// 次のキー・値ペアに続く
continue
} else {
if me.match_token(TokenType.RBRACE()) {
if me.match_token("RBRACE") {
break // オブジェクト終了
} else {
me.add_error("Expected ',' or '}' in object")
@ -215,7 +209,7 @@ box JsonParser {
// 配列解析
parse_array() {
local start_token = me.current_token()
if start_token == null or start_token.get_type() != TokenType.LBRACKET() {
if start_token == null or start_token.get_type() != "LBRACKET" {
me.add_error("Expected '[' to start array")
return null
}
@ -224,7 +218,7 @@ box JsonParser {
local array_node = JsonNode.create_array()
// 空配列チェック
if me.match_token(TokenType.RBRACKET()) {
if me.match_token("RBRACKET") {
return array_node
}
@ -240,11 +234,11 @@ box JsonParser {
array_node.array_push(value)
// 継続判定
if me.match_token(TokenType.COMMA()) {
if me.match_token("COMMA") {
// 次の要素に続く
continue
} else {
if me.match_token(TokenType.RBRACKET()) {
if me.match_token("RBRACKET") {
break // 配列終了
} else {
me.add_error("Expected ',' or ']' in array")
@ -284,9 +278,8 @@ box JsonParser {
// 指定されたトークンタイプにマッチするかチェック(マッチしたら消費)
match_token(expected_type) {
local token = me.current_token()
if token != null and token.get_type() == expected_type {
me.advance()
return true
if token != null {
if token.get_type() == expected_type { me.advance() return true }
}
return false
}