feat: 改行処理Phase 1 TokenCursor基本実装完了

- src/parser/cursor.rs: TokenCursor本体実装(230行)
  - NewlineMode(Stmt/Expr)による文脈認識改行処理
  - ブレース/パーレン/ブラケット深度の自動追跡
  - 行継続判定(演算子・カンマ等)
  - with_expr_mode/with_stmt_mode によるモード切替
- src/parser/expr_cursor.rs: TokenCursor版式パーサー(250行)
  - 実験的実装として式パーサーを TokenCursor対応
  - 二項演算子・比較・プライマリ式・オブジェクトリテラル対応
- ビルド成功(warning のみ、エラーなし)
- CLAUDE.md更新: Phase 1実装内容を記載

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 10:24:40 +09:00
parent 75b42bbff5
commit bbde1d3d70
4 changed files with 567 additions and 10 deletions

View File

@ -397,20 +397,25 @@ jq '.functions[0].blocks' mir.json # ブロック構造確認
- 🗃️ **アーカイブ整理**: 古いphaseファイル群をarchiveに移動、導線クリーンアップ完了 - 🗃️ **アーカイブ整理**: 古いphaseファイル群をarchiveに移動、導線クリーンアップ完了
- 📋 詳細: [Property System仕様](docs/proposals/unified-members.md) | [Python統合計画](docs/development/roadmap/phases/phase-10.7/) - 📋 詳細: [Property System仕様](docs/proposals/unified-members.md) | [Python統合計画](docs/development/roadmap/phases/phase-10.7/)
## 📝 Update (2025-09-24) ✅ 改行処理Phase 0 Quick Fix完了! ## 📝 Update (2025-09-24) ✅ 改行処理Phase 0 & Phase 1基本実装完了!
-**複数行match式パース成功** たった5箇所の修正で複数行オブジェクトリテラル完全動作 -**Phase 0 Quick Fix完了** たった5箇所の修正で複数行オブジェクトリテラル完全動作
- **修正箇所**: - `primary.rs`: COLON前後とCOMMA判定前にskip_newlines()追加3箇所
1. `primary.rs`: COLON前後とCOMMA判定前にskip_newlines()追加3箇所 - `match_expr.rs`: is_object_literal()関数を改行対応lookahead改良
2. `match_expr.rs`: is_object_literal()関数を改行対応lookahead改良
- **必須環境変数**: `NYASH_SYNTAX_SUGAR_LEVEL=full`IDENTIFIERキー使用のため - **必須環境変数**: `NYASH_SYNTAX_SUGAR_LEVEL=full`IDENTIFIERキー使用のため
- **テスト結果**: MapBox正常出力 `{'__box__': 'MapBox', '__map': {'value': 42, 'name': 'answer'}}` - **テスト結果**: MapBox正常出力 `{'__box__': 'MapBox', '__map': {'value': 42, 'name': 'answer'}}`
- 🎯 **Phase 1 TokenCursor基本実装完了** 改行処理を一元管理する仕組み構築
- **新規実装ファイル**:
1. `src/parser/cursor.rs`: TokenCursor本体230行- モード制御・深度追跡・自動改行処理
2. `src/parser/expr_cursor.rs`: TokenCursor版式パーサー250行- 実験的実装
- **主要機能**:
- NewlineModeStmt/Exprによる文脈認識改行処理
- ブレース/パーレン/ブラケット深度の自動追跡
- 行継続判定(演算子・カンマ等)
- with_expr_mode/with_stmt_mode によるモード一時切り替え
- **ビルド成功**: warning のみでエラーなし
- 🎯 **セミコロンモード確認完了!** `NYASH_PARSER_ALLOW_SEMICOLON=1`で動作確認 - 🎯 **セミコロンモード確認完了!** `NYASH_PARSER_ALLOW_SEMICOLON=1`で動作確認
- セミコロンと改行を自由に混在可能
- JavaScript/Go風の書き方も完全サポート
- 📚 **改行処理戦略ドキュメント完成**: [newline-handling-strategy.md](docs/development/strategies/newline-handling-strategy.md) - 📚 **改行処理戦略ドキュメント完成**: [newline-handling-strategy.md](docs/development/strategies/newline-handling-strategy.md)
- 3段階実装計画Phase 0: Quick Fix ✅, Phase 1: TokenCursor, Phase 2: LASI - 🚀 **次の実装**: Phase 2 LASI前処理でトークン正規化Phase 15後
- ChatGPT Pro提案のTokenCursorサンプルコード含む
- 🚀 **次の実装**: Phase 1 TokenCursor導入で改行処理を根本解決へ
## 📝 Update (2025-09-23) ✅ フェーズS実装完了break制御フロー根治開始 ## 📝 Update (2025-09-23) ✅ フェーズS実装完了break制御フロー根治開始
-**フェーズS完了** PHI incoming修正+終端ガード徹底→重複処理4箇所統一 -**フェーズS完了** PHI incoming修正+終端ガード徹底→重複処理4箇所統一

297
src/parser/cursor.rs Normal file
View File

@ -0,0 +1,297 @@
use crate::tokenizer::{Token, TokenType};
/// トークンカーソル - 改行処理を一元管理
#[derive(Debug)]
pub struct TokenCursor<'a> {
tokens: &'a [Token],
idx: usize,
mode: NewlineMode,
paren_depth: usize, // ()
brace_depth: usize, // {}
bracket_depth: usize, // []
}
/// 改行処理モード
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewlineMode {
/// 文モード:改行は文の区切り
Stmt,
/// 式モード:改行を自動スキップ
Expr,
}
impl<'a> TokenCursor<'a> {
/// 新しいTokenCursorを作成
pub fn new(tokens: &'a [Token]) -> Self {
Self {
tokens,
idx: 0,
mode: NewlineMode::Stmt,
paren_depth: 0,
brace_depth: 0,
bracket_depth: 0,
}
}
/// 現在のトークンを取得
pub fn current(&self) -> &Token {
self.tokens.get(self.idx).unwrap_or(&Token {
token_type: TokenType::EOF,
line: 0,
column: 0,
})
}
/// 次のトークンをピーク
pub fn peek(&self) -> &Token {
self.tokens.get(self.idx + 1).unwrap_or(&Token {
token_type: TokenType::EOF,
line: 0,
column: 0,
})
}
/// N番目のトークンをピーク
pub fn peek_nth(&self, n: usize) -> &Token {
self.tokens.get(self.idx + n).unwrap_or(&Token {
token_type: TokenType::EOF,
line: 0,
column: 0,
})
}
/// 次のトークンに進む(改行を考慮)
pub fn advance(&mut self) {
if self.idx < self.tokens.len() {
// 深度を更新
match &self.tokens[self.idx].token_type {
TokenType::LPAREN => self.paren_depth += 1,
TokenType::RPAREN => self.paren_depth = self.paren_depth.saturating_sub(1),
TokenType::LBRACE => self.brace_depth += 1,
TokenType::RBRACE => self.brace_depth = self.brace_depth.saturating_sub(1),
TokenType::LBRACK => self.bracket_depth += 1,
TokenType::RBRACK => self.bracket_depth = self.bracket_depth.saturating_sub(1),
_ => {}
}
self.idx += 1;
// 改行を自動的にスキップするかチェック
while self.should_skip_newline() && self.idx < self.tokens.len() {
if matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE) {
self.idx += 1;
} else {
break;
}
}
}
}
/// 明示的に改行をスキップ
pub fn skip_newlines(&mut self) {
while self.idx < self.tokens.len()
&& matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE) {
self.idx += 1;
}
}
/// トークンが期待した型かチェック
pub fn match_token(&self, token_type: &TokenType) -> bool {
std::mem::discriminant(&self.current().token_type) == std::mem::discriminant(token_type)
}
/// 期待したトークンを消費
pub fn consume(&mut self, expected: TokenType) -> Result<(), crate::parser::ParseError> {
if self.match_token(&expected) {
self.advance();
Ok(())
} else {
Err(crate::parser::ParseError::UnexpectedToken {
found: self.current().token_type.clone(),
expected: format!("{:?}", expected),
line: self.current().line,
})
}
}
/// ファイル終端かチェック
pub fn is_at_end(&self) -> bool {
matches!(self.current().token_type, TokenType::EOF)
}
/// 式モードで一時的に実行
pub fn with_expr_mode<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
let old_mode = self.mode;
self.mode = NewlineMode::Expr;
let result = f(self);
self.mode = old_mode;
result
}
/// 文モードで一時的に実行
pub fn with_stmt_mode<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
let old_mode = self.mode;
self.mode = NewlineMode::Stmt;
let result = f(self);
self.mode = old_mode;
result
}
/// 改行をスキップすべきか判定
fn should_skip_newline(&self) -> bool {
// ブレース/パーレン/ブラケット内では常にスキップ
if self.brace_depth > 0 || self.paren_depth > 0 || self.bracket_depth > 0 {
return true;
}
// 式モードでは改行をスキップ
if self.mode == NewlineMode::Expr {
return true;
}
// 行継続判定(直前のトークンを見る)
if self.prev_is_line_continuation() {
return true;
}
false
}
/// 直前のトークンが行継続を示すか判定
fn prev_is_line_continuation(&self) -> bool {
if self.idx == 0 {
return false;
}
match &self.tokens[self.idx - 1].token_type {
// 二項演算子
TokenType::PLUS | TokenType::MINUS | TokenType::MULTIPLY | TokenType::DIVIDE |
TokenType::MODULO | TokenType::AND | TokenType::OR |
TokenType::BitOr | TokenType::BitAnd | TokenType::BitXor |
// メンバアクセス
TokenType::DOT | TokenType::DoubleColon |
// Optional系
TokenType::QUESTION |
// Arrow
TokenType::FatArrow |
// カンマ
TokenType::COMMA => true,
_ => false,
}
}
/// 現在の位置を取得
pub fn position(&self) -> usize {
self.idx
}
/// 位置を設定(バックトラック用)
pub fn set_position(&mut self, pos: usize) {
if pos <= self.tokens.len() {
self.idx = pos;
// 深度を再計算
self.recalculate_depths();
}
}
/// 深度を再計算
fn recalculate_depths(&mut self) {
self.paren_depth = 0;
self.brace_depth = 0;
self.bracket_depth = 0;
for i in 0..self.idx {
match &self.tokens[i].token_type {
TokenType::LPAREN => self.paren_depth += 1,
TokenType::RPAREN => self.paren_depth = self.paren_depth.saturating_sub(1),
TokenType::LBRACE => self.brace_depth += 1,
TokenType::RBRACE => self.brace_depth = self.brace_depth.saturating_sub(1),
TokenType::LBRACK => self.bracket_depth += 1,
TokenType::RBRACK => self.bracket_depth = self.bracket_depth.saturating_sub(1),
_ => {}
}
}
}
/// モードを取得
pub fn get_mode(&self) -> NewlineMode {
self.mode
}
/// モードを設定
pub fn set_mode(&mut self, mode: NewlineMode) {
self.mode = mode;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_cursor_operations() {
let tokens = vec![
Token { token_type: TokenType::LOCAL, line: 1, column: 1 },
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 7 },
Token { token_type: TokenType::ASSIGN, line: 1, column: 9 },
Token { token_type: TokenType::NUMBER(42), line: 1, column: 11 },
Token { token_type: TokenType::EOF, line: 1, column: 13 },
];
let mut cursor = TokenCursor::new(&tokens);
assert!(cursor.match_token(&TokenType::LOCAL));
cursor.advance();
assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_)));
cursor.advance();
assert!(cursor.match_token(&TokenType::ASSIGN));
cursor.advance();
assert!(matches!(cursor.current().token_type, TokenType::NUMBER(42)));
cursor.advance();
assert!(cursor.is_at_end());
}
#[test]
fn test_newline_skipping_in_braces() {
let tokens = vec![
Token { token_type: TokenType::LBRACE, line: 1, column: 1 },
Token { token_type: TokenType::NEWLINE, line: 1, column: 2 },
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 2, column: 1 },
Token { token_type: TokenType::RBRACE, line: 2, column: 2 },
Token { token_type: TokenType::EOF, line: 2, column: 3 },
];
let mut cursor = TokenCursor::new(&tokens);
cursor.advance(); // consume LBRACE, should skip NEWLINE
assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_)));
}
#[test]
fn test_expr_mode() {
let tokens = vec![
Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 1 },
Token { token_type: TokenType::NEWLINE, line: 1, column: 2 },
Token { token_type: TokenType::PLUS, line: 2, column: 1 },
Token { token_type: TokenType::NUMBER(1), line: 2, column: 3 },
Token { token_type: TokenType::EOF, line: 2, column: 4 },
];
let mut cursor = TokenCursor::new(&tokens);
cursor.with_expr_mode(|c| {
c.advance(); // consume IDENTIFIER, should skip NEWLINE in expr mode
assert!(c.match_token(&TokenType::PLUS));
});
}
}

253
src/parser/expr_cursor.rs Normal file
View File

@ -0,0 +1,253 @@
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::parser::cursor::TokenCursor;
use crate::parser::ParseError;
use crate::tokenizer::TokenType;
/// TokenCursorを使用した式パーサー実験的実装
pub struct ExprParserWithCursor;
impl ExprParserWithCursor {
/// 式をパースTokenCursor版
pub fn parse_expression(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
// 式モードで実行(改行を自動的にスキップ)
cursor.with_expr_mode(|c| {
Self::parse_or_expr(c)
})
}
/// OR式をパース
fn parse_or_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_and_expr(cursor)?;
while cursor.match_token(&TokenType::OR) {
let op_line = cursor.current().line;
cursor.advance();
let right = Self::parse_and_expr(cursor)?;
left = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(left),
right: Box::new(right),
span: Span::new(op_line, 0, op_line, 0),
};
}
Ok(left)
}
/// AND式をパース
fn parse_and_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_comparison_expr(cursor)?;
while cursor.match_token(&TokenType::AND) {
let op_line = cursor.current().line;
cursor.advance();
let right = Self::parse_comparison_expr(cursor)?;
left = ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(left),
right: Box::new(right),
span: Span::new(op_line, 0, op_line, 0),
};
}
Ok(left)
}
/// 比較式をパース
fn parse_comparison_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_additive_expr(cursor)?;
while let Some(op) = Self::match_comparison_op(cursor) {
let op_line = cursor.current().line;
cursor.advance();
let right = Self::parse_additive_expr(cursor)?;
left = ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: Span::new(op_line, 0, op_line, 0),
};
}
Ok(left)
}
/// 比較演算子をチェック
fn match_comparison_op(cursor: &TokenCursor) -> Option<BinaryOperator> {
match &cursor.current().token_type {
TokenType::EQUALS => Some(BinaryOperator::Equal),
TokenType::NotEquals => Some(BinaryOperator::NotEqual),
TokenType::LESS => Some(BinaryOperator::Less),
TokenType::LessEquals => Some(BinaryOperator::LessEqual),
TokenType::GREATER => Some(BinaryOperator::Greater),
TokenType::GreaterEquals => Some(BinaryOperator::GreaterEqual),
_ => None,
}
}
/// 加算式をパース
fn parse_additive_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_multiplicative_expr(cursor)?;
while let Some(op) = Self::match_additive_op(cursor) {
let op_line = cursor.current().line;
cursor.advance();
let right = Self::parse_multiplicative_expr(cursor)?;
left = ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: Span::new(op_line, 0, op_line, 0),
};
}
Ok(left)
}
/// 加算演算子をチェック
fn match_additive_op(cursor: &TokenCursor) -> Option<BinaryOperator> {
match &cursor.current().token_type {
TokenType::PLUS => Some(BinaryOperator::Add),
TokenType::MINUS => Some(BinaryOperator::Subtract),
_ => None,
}
}
/// 乗算式をパース
fn parse_multiplicative_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
let mut left = Self::parse_primary_expr(cursor)?;
while let Some(op) = Self::match_multiplicative_op(cursor) {
let op_line = cursor.current().line;
cursor.advance();
let right = Self::parse_primary_expr(cursor)?;
left = ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: Span::new(op_line, 0, op_line, 0),
};
}
Ok(left)
}
/// 乗算演算子をチェック
fn match_multiplicative_op(cursor: &TokenCursor) -> Option<BinaryOperator> {
match &cursor.current().token_type {
TokenType::MULTIPLY => Some(BinaryOperator::Multiply),
TokenType::DIVIDE => Some(BinaryOperator::Divide),
TokenType::MODULO => Some(BinaryOperator::Modulo),
_ => None,
}
}
/// プライマリ式をパース
fn parse_primary_expr(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
match &cursor.current().token_type.clone() {
TokenType::NUMBER(n) => {
let value = *n;
cursor.advance();
Ok(ASTNode::Literal {
value: LiteralValue::Integer(value),
span: Span::unknown(),
})
}
TokenType::STRING(s) => {
let value = s.clone();
cursor.advance();
Ok(ASTNode::Literal {
value: LiteralValue::String(value),
span: Span::unknown(),
})
}
TokenType::TRUE => {
cursor.advance();
Ok(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
})
}
TokenType::FALSE => {
cursor.advance();
Ok(ASTNode::Literal {
value: LiteralValue::Bool(false),
span: Span::unknown(),
})
}
TokenType::NULL => {
cursor.advance();
Ok(ASTNode::Literal {
value: LiteralValue::Null,
span: Span::unknown(),
})
}
TokenType::IDENTIFIER(name) => {
let name = name.clone();
cursor.advance();
Ok(ASTNode::Variable {
name,
span: Span::unknown(),
})
}
TokenType::LPAREN => {
cursor.advance();
let expr = Self::parse_expression(cursor)?;
cursor.consume(TokenType::RPAREN)?;
Ok(expr)
}
TokenType::LBRACE => {
// オブジェクトリテラル(改行対応済み)
Self::parse_object_literal(cursor)
}
_ => {
let line = cursor.current().line;
Err(ParseError::InvalidExpression { line })
}
}
}
/// オブジェクトリテラルをパースTokenCursor版
fn parse_object_literal(cursor: &mut TokenCursor) -> Result<ASTNode, ParseError> {
cursor.consume(TokenType::LBRACE)?;
let mut entries = Vec::new();
while !cursor.match_token(&TokenType::RBRACE) && !cursor.is_at_end() {
// キーをパースSTRING or IDENTIFIER
let key = match &cursor.current().token_type {
TokenType::STRING(s) => {
let k = s.clone();
cursor.advance();
k
}
TokenType::IDENTIFIER(id) => {
let k = id.clone();
cursor.advance();
k
}
_ => {
let line = cursor.current().line;
return Err(ParseError::UnexpectedToken {
found: cursor.current().token_type.clone(),
expected: "string or identifier key".to_string(),
line,
});
}
};
cursor.consume(TokenType::COLON)?;
let value = Self::parse_expression(cursor)?;
entries.push((key, value));
if cursor.match_token(&TokenType::COMMA) {
cursor.advance();
}
}
cursor.consume(TokenType::RBRACE)?;
Ok(ASTNode::MapLiteral {
entries,
span: Span::unknown(),
})
}
}

View File

@ -18,9 +18,11 @@
// サブモジュール宣言 // サブモジュール宣言
mod common; mod common;
mod cursor; // TokenCursor: 改行処理を一元管理
mod declarations; mod declarations;
pub mod entry_sugar; // helper to parse with sugar level pub mod entry_sugar; // helper to parse with sugar level
mod expr; mod expr;
mod expr_cursor; // TokenCursorを使用した式パーサー実験的
mod expressions; mod expressions;
mod items; mod items;
mod statements; mod statements;