347 lines
9.7 KiB
Plaintext
347 lines
9.7 KiB
Plaintext
|
|
// JsonScanner — 文字レベルのスキャン(美しいモジュラー設計)
|
|||
|
|
// 責務: 文字単位での読み取り・位置管理・先読み機能
|
|||
|
|
|
|||
|
|
// StringUtils dependency removed - using internal methods
|
|||
|
|
|
|||
|
|
// 🔍 JSON文字スキャナー(Everything is Box)
|
|||
|
|
static box JsonScannerModule {
|
|||
|
|
create_scanner(input_text) {
|
|||
|
|
return new JsonScanner(input_text)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
box JsonScanner {
|
|||
|
|
text: StringBox // 入力文字列
|
|||
|
|
position: IntegerBox // 現在位置
|
|||
|
|
length: IntegerBox // 文字列長
|
|||
|
|
line: IntegerBox // 現在行番号
|
|||
|
|
column: IntegerBox // 現在列番号
|
|||
|
|
|
|||
|
|
birth(input_text) {
|
|||
|
|
me.text = input_text
|
|||
|
|
me.position = 0
|
|||
|
|
me.length = input_text.length()
|
|||
|
|
me.line = 1
|
|||
|
|
me.column = 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 基本読み取りメソッド =====
|
|||
|
|
|
|||
|
|
// 現在文字を取得(EOF時は空文字列)
|
|||
|
|
current() {
|
|||
|
|
if me.position >= me.length {
|
|||
|
|
return "" // EOF
|
|||
|
|
}
|
|||
|
|
return me.text.substring(me.position, me.position + 1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 次の文字を先読み(位置は移動しない)
|
|||
|
|
peek() {
|
|||
|
|
if me.position + 1 >= me.length {
|
|||
|
|
return "" // EOF
|
|||
|
|
}
|
|||
|
|
return me.text.substring(me.position + 1, me.position + 2)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// n文字先を先読み
|
|||
|
|
peek_at(offset) {
|
|||
|
|
local pos = me.position + offset
|
|||
|
|
if pos >= me.length {
|
|||
|
|
return "" // EOF
|
|||
|
|
}
|
|||
|
|
return me.text.substring(pos, pos + 1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 現在位置から指定長の文字列を取得
|
|||
|
|
substring(len) {
|
|||
|
|
local end_pos = me.position + len
|
|||
|
|
if end_pos > me.length {
|
|||
|
|
end_pos = me.length
|
|||
|
|
}
|
|||
|
|
return me.text.substring(me.position, end_pos)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 位置制御メソッド =====
|
|||
|
|
|
|||
|
|
// 1文字進む
|
|||
|
|
advance() {
|
|||
|
|
if me.position < me.length {
|
|||
|
|
local ch = me.current()
|
|||
|
|
me.position = me.position + 1
|
|||
|
|
|
|||
|
|
// 行番号・列番号の更新
|
|||
|
|
if ch == "\n" {
|
|||
|
|
me.line = me.line + 1
|
|||
|
|
me.column = 1
|
|||
|
|
} else {
|
|||
|
|
me.column = me.column + 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ch
|
|||
|
|
}
|
|||
|
|
return "" // EOF
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// n文字進む
|
|||
|
|
advance_by(n) {
|
|||
|
|
local i = 0
|
|||
|
|
loop(i < n) {
|
|||
|
|
if me.is_eof() {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 指定位置にジャンプ(行番号は再計算)
|
|||
|
|
seek(pos) {
|
|||
|
|
if pos < 0 {
|
|||
|
|
pos = 0
|
|||
|
|
}
|
|||
|
|
if pos > me.length {
|
|||
|
|
pos = me.length
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
me.position = pos
|
|||
|
|
me.recalculate_line_column()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 状態チェックメソッド =====
|
|||
|
|
|
|||
|
|
// EOFかどうか
|
|||
|
|
is_eof() {
|
|||
|
|
return me.position >= me.length
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 残り文字数
|
|||
|
|
remaining() {
|
|||
|
|
return me.length - me.position
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 現在位置を取得
|
|||
|
|
get_position() {
|
|||
|
|
return me.position
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get_line() {
|
|||
|
|
return me.line
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get_column() {
|
|||
|
|
return me.column
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 空白文字判定(内蔵)
|
|||
|
|
is_whitespace_char(ch) {
|
|||
|
|
return ch == " " or ch == "\t" or ch == "\n" or ch == "\r"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 数字文字判定(内蔵)
|
|||
|
|
is_digit_char(ch) {
|
|||
|
|
return ch == "0" or ch == "1" or ch == "2" or ch == "3" or ch == "4" or ch == "5" or ch == "6" or ch == "7" or ch == "8" or ch == "9"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 16進数字判定(内蔵)
|
|||
|
|
is_hex_digit_char(ch) {
|
|||
|
|
return me.is_digit_char(ch) or ch == "a" or ch == "b" or ch == "c" or ch == "d" or ch == "e" or ch == "f" or ch == "A" or ch == "B" or ch == "C" or ch == "D" or ch == "E" or ch == "F"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 高レベル読み取りメソッド =====
|
|||
|
|
|
|||
|
|
// 空白をスキップ
|
|||
|
|
skip_whitespace() {
|
|||
|
|
loop(not me.is_eof()) {
|
|||
|
|
local ch = me.current()
|
|||
|
|
if not me.is_whitespace_char(ch) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 指定文字列にマッチするかチェック(マッチしたら消費)
|
|||
|
|
match_string(expected) {
|
|||
|
|
if me.remaining() < expected.length() {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
local actual = me.text.substring(me.position, me.position + expected.length())
|
|||
|
|
if actual == expected {
|
|||
|
|
me.advance_by(expected.length())
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 指定文字列の先読みチェック(位置は移動しない)
|
|||
|
|
starts_with(prefix) {
|
|||
|
|
if me.remaining() < prefix.length() {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
local actual = me.text.substring(me.position, me.position + prefix.length())
|
|||
|
|
return actual == prefix
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 条件を満たす間読み取り続ける
|
|||
|
|
read_while(condition_fn) {
|
|||
|
|
local start_pos = me.position
|
|||
|
|
|
|||
|
|
loop(not me.is_eof()) {
|
|||
|
|
local ch = me.current()
|
|||
|
|
if not condition_fn(ch) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return me.text.substring(start_pos, me.position)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 数値文字列を読み取り
|
|||
|
|
read_number() {
|
|||
|
|
local start_pos = me.position
|
|||
|
|
|
|||
|
|
// マイナス符号
|
|||
|
|
if me.current() == "-" {
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 整数部分
|
|||
|
|
if me.current() == "0" {
|
|||
|
|
me.advance()
|
|||
|
|
} else {
|
|||
|
|
if me.is_digit_char(me.current()) {
|
|||
|
|
loop(not me.is_eof() and me.is_digit_char(me.current())) {
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return null // 無効な数値
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 小数部分(オプション)
|
|||
|
|
if me.current() == "." {
|
|||
|
|
me.advance()
|
|||
|
|
if not me.is_digit_char(me.current()) {
|
|||
|
|
return null // 無効な小数
|
|||
|
|
}
|
|||
|
|
loop(not me.is_eof() and me.is_digit_char(me.current())) {
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 指数部分(オプション)
|
|||
|
|
local ch = me.current()
|
|||
|
|
if ch == "e" or ch == "E" {
|
|||
|
|
me.advance()
|
|||
|
|
ch = me.current()
|
|||
|
|
if ch == "+" or ch == "-" {
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
if not me.is_digit_char(me.current()) {
|
|||
|
|
return null // 無効な指数
|
|||
|
|
}
|
|||
|
|
loop(not me.is_eof() and me.is_digit_char(me.current())) {
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return me.text.substring(start_pos, me.position)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 文字列リテラルを読み取り(クォート含む)
|
|||
|
|
read_string_literal() {
|
|||
|
|
local start_pos = me.position
|
|||
|
|
|
|||
|
|
// 開始クォート
|
|||
|
|
if me.current() != "\"" {
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
|
|||
|
|
// 文字列内容
|
|||
|
|
loop(not me.is_eof()) {
|
|||
|
|
local ch = me.current()
|
|||
|
|
|
|||
|
|
if ch == "\"" {
|
|||
|
|
// 終了クォート
|
|||
|
|
me.advance()
|
|||
|
|
return me.text.substring(start_pos, me.position)
|
|||
|
|
} else {
|
|||
|
|
if ch == "\\" {
|
|||
|
|
// エスケープシーケンス
|
|||
|
|
me.advance()
|
|||
|
|
if me.is_eof() {
|
|||
|
|
return null // 不完全なエスケープ
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
local escaped = me.current()
|
|||
|
|
if escaped == "u" {
|
|||
|
|
// Unicode エスケープ \uXXXX
|
|||
|
|
me.advance()
|
|||
|
|
local i = 0
|
|||
|
|
loop(i < 4) {
|
|||
|
|
if me.is_eof() or not me.is_hex_digit_char(me.current()) {
|
|||
|
|
return null // 無効なUnicodeエスケープ
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 通常のエスケープ
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if ch == "\n" or ch == "\r" {
|
|||
|
|
return null // 文字列内の生の改行は無効
|
|||
|
|
}
|
|||
|
|
me.advance()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null // 終了クォートが見つからない
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== プライベートメソッド =====
|
|||
|
|
|
|||
|
|
// 行番号・列番号を再計算(seek後に使用)
|
|||
|
|
recalculate_line_column() {
|
|||
|
|
me.line = 1
|
|||
|
|
me.column = 1
|
|||
|
|
|
|||
|
|
local i = 0
|
|||
|
|
loop(i < me.position) {
|
|||
|
|
local ch = me.text.substring(i, i + 1)
|
|||
|
|
if ch == "\n" {
|
|||
|
|
me.line = me.line + 1
|
|||
|
|
me.column = 1
|
|||
|
|
} else {
|
|||
|
|
me.column = me.column + 1
|
|||
|
|
}
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== デバッグメソッド =====
|
|||
|
|
|
|||
|
|
get_context(radius) {
|
|||
|
|
local start = me.position - radius
|
|||
|
|
if start < 0 { start = 0 }
|
|||
|
|
|
|||
|
|
local end = me.position + radius
|
|||
|
|
if end > me.length { end = me.length }
|
|||
|
|
|
|||
|
|
local before = me.text.substring(start, me.position)
|
|||
|
|
local current = me.current()
|
|||
|
|
local after = me.text.substring(me.position + 1, end)
|
|||
|
|
|
|||
|
|
return before + "【" + current + "】" + after
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
to_debug_string() {
|
|||
|
|
return "Scanner{pos:" + me.position + "/" + me.length + ", line:" + me.line + ", col:" + me.column + ", context:'" + me.get_context(5) + "'}"
|
|||
|
|
}
|
|||
|
|
}
|