- primary.rsに3箇所のskip_newlines()追加(COLON前後、COMMA判定前) - match_expr.rsのis_object_literal()を改行対応(lookahead改良) - セミコロンモード確認(NYASH_PARSER_ALLOW_SEMICOLON=1) - テストケース全て成功(NYASH_SYNTAX_SUGAR_LEVEL=full必須) - CLAUDE.md更新、改行処理戦略ドキュメント作成済み 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
6.8 KiB
6.8 KiB
Nyash改行処理戦略
作成日: 2025-09-23 ステータス: 承認済み・実装開始
概要
Nyashパーサーにおける改行処理の統一戦略。現在のskip_newlines()散在問題を解決し、コンテキスト認識による自動改行処理を実現する。
現状の問題
1. skip_newlines()の散在
// 現状:各所で手動呼び出しが必要
fn parse_object_literal() {
self.skip_newlines(); // ここにも
self.consume(TokenType::COLON)?;
self.skip_newlines(); // あそこにも
let value = self.parse_expression()?;
self.skip_newlines(); // どこにでも
}
2. 複数行構文のパースエラー
// これがパースできない
local result = match "test" {
"test" => {
value: 42, // ← 改行でエラー
name: "answer"
}
}
3. 保守性の問題
- 新しい構文追加時に
skip_newlines()の配置を忘れやすい - 一貫性のない改行処理
- デバッグが困難
設計原則
1. セミコロンオプショナル
- 改行もセミコロンも文の区切りとして扱う
- ユーザーは好みのスタイルを選択可能
2. コンテキスト認識
{},[],()内では改行を自動的に無視- 式コンテキストでは改行をスキップ
- 文コンテキストでは改行を文区切りとして扱う
3. 環境変数地獄の回避
- コンテキストベースの制御を優先
- 環境変数は互換性のためのみに限定
実装戦略(3段階)
Phase 0: Quick Fix(即座実装)
目的: 緊急対応として最小限の修正で問題を解決
// src/parser/expr/primary.rs L77付近
self.skip_newlines(); // COLON前に追加
self.consume(TokenType::COLON)?;
self.skip_newlines(); // 値パース前に追加
let value_expr = self.parse_expression()?;
self.skip_newlines(); // COMMA判定前に追加
期間: 30分 効果: match式の複数行オブジェクトリテラルが動作
Phase 1: TokenCursor導入(今週実装)
目的: 改行処理を一元管理し、skip_newlines()散在を根絶
// src/parser/cursor.rs(新規)
pub struct TokenCursor<'a> {
tokens: &'a [Token],
idx: usize,
mode: NewlineMode,
paren_depth: usize,
brace_depth: usize,
bracket_depth: usize,
}
pub enum NewlineMode {
Stmt, // 文モード:改行は文区切り
Expr, // 式モード:改行を自動スキップ
}
impl<'a> TokenCursor<'a> {
pub fn with_expr_mode<F, T>(&mut self, f: F) -> T
where F: FnOnce(&mut Self) -> T {
let old = self.mode;
self.mode = NewlineMode::Expr;
let result = f(self);
self.mode = old;
result
}
fn should_skip_newline(&self) -> bool {
// ブレース内 or 式モード or 行継続
self.brace_depth > 0 ||
self.mode == NewlineMode::Expr ||
self.prev_is_line_continuation()
}
}
使用例:
fn parse_expression(cursor: &mut TokenCursor) -> Result<Expr> {
cursor.with_expr_mode(|c| {
// この中では改行が自動的にスキップされる
parse_binary_expr(c, 0)
})
}
期間: 1週間 効果:
- 改行処理の一元化
- 新規構文追加時のミス防止
- デバッグの容易化
Phase 2: LASI前処理(将来実装)
目的: トークンレベルで改行を正規化し、パーサーを単純化
// トークン正規化層
fn normalize_tokens(tokens: Vec<Token>) -> Vec<Token> {
let mut result = Vec::new();
let mut iter = tokens.into_iter();
while let Some(token) = iter.next() {
match token.token_type {
TokenType::NEWLINE => {
if should_insert_eol(&prev, &next) {
result.push(Token::EOL);
}
// それ以外のNEWLINEは削除
}
_ => result.push(token),
}
}
result
}
期間: Phase 15完了後 効果:
- パーサーの大幅な簡略化
- 完全な改行処理の分離
- LosslessToken/Triviaとの統合
行継続ルール
継続と判定する記号(直前)
- 二項演算子:
+,-,*,/,%,&&,||,|,&,^ - メンバアクセス:
.,:: - Optional系:
?,?.,?? - Arrow:
=>,-> - カンマ:
,
文終端強制(ハザード回避)
return,break,continueの直後の改行は常に文終端- JavaScriptのASIハザードと同様の考え方
テストケース
基本ケース
// 2文として解釈
a = 1
b = 2
// 1文として解釈(行継続)
a = 1 +
2
// セミコロンも可
a = 1; b = 2
括弧内改行
// すべてOK
f(
arg1,
arg2
)
[
1,
2,
3
]
{
key1: value1,
key2: value2
}
match式
local result = match value {
"test" => {
name: "foo",
value: 42
},
_ => {
name: "default",
value: 0
}
}
returnハザード
return // returnのみ
42 // 別の文として解釈
return 42 // return 42として解釈
影響分析
後方互換性
- Phase 0: 完全互換
- Phase 1: 完全互換(内部実装の変更のみ)
- Phase 2: セミコロン使用時のみ互換性確認必要
パフォーマンス
- Phase 0: 影響なし
- Phase 1: わずかなオーバーヘッド(カーソル管理)
- Phase 2: 前処理により若干の初期化コスト
参考:他言語の実装
Go
- トークナイザレベルでセミコロン自動挿入
- 特定トークン後に改行があれば
;を挿入
JavaScript
- ASI(Automatic Semicolon Insertion)
- returnハザード等の特殊ルールあり
Python
- インデントベース(Nyashとは異なるアプローチ)
Rust
- セミコロン必須(式vs文の区別)
決定事項
-
TokenCursorアプローチを採用
- ChatGPT提案のサンプルコードをベースに実装
- 環境変数ではなくコンテキストベースの制御
-
3段階実装で段階的改善
- まずQuick Fixで緊急対応
- TokenCursorで本質的解決
- 将来的にLASI前処理で完成形へ
-
セミコロン完全オプショナル
- 改行とセミコロンを同等に扱う
- ユーザーの好みに応じて選択可能
実装タイムライン
- 2025-09-23: Quick Fix実装(30分)
- 2025-09-30: TokenCursor Phase 1完了(1週間)
- Phase 15後: LASI前処理検討開始
関連ドキュメント
承認
- ChatGPT Pro: 技術分析・実装提案
- Claude: 実装戦略決定・ドキュメント化
- 実装開始: 2025-09-23