370 lines
10 KiB
Plaintext
370 lines
10 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"
|
||
}
|
||
|
||
// 英字判定(内蔵)
|
||
is_alpha_char(ch) {
|
||
return (ch >= "a" and ch <= "z") or (ch >= "A" and ch <= "Z")
|
||
}
|
||
|
||
// 英数字 + 下線(識別子向け)
|
||
is_alphanumeric_or_underscore(ch) {
|
||
return me.is_alpha_char(ch) or me.is_digit_char(ch) or ch == "_"
|
||
}
|
||
|
||
// ===== 高レベル読み取りメソッド =====
|
||
|
||
// 空白をスキップ
|
||
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_identifier() {
|
||
local start_pos = me.position
|
||
if not me.is_alpha_char(me.current()) and me.current() != "_" {
|
||
return ""
|
||
}
|
||
loop(not me.is_eof() and me.is_alphanumeric_or_underscore(me.current())) {
|
||
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) + "'}"
|
||
}
|
||
}
|