diff --git a/CLAUDE.md b/CLAUDE.md index 316301ce..30941a18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -397,20 +397,25 @@ jq '.functions[0].blocks' mir.json # ブロック構造確認 - 🗃️ **アーカイブ整理**: 古いphaseファイル群をarchiveに移動、導線クリーンアップ完了 - 📋 詳細: [Property System仕様](docs/proposals/unified-members.md) | [Python統合計画](docs/development/roadmap/phases/phase-10.7/) -## 📝 Update (2025-09-24) ✅ 改行処理Phase 0 Quick Fix完了! -- ✅ **複数行match式パース成功!** たった5箇所の修正で複数行オブジェクトリテラル完全動作 - - **修正箇所**: - 1. `primary.rs`: COLON前後とCOMMA判定前にskip_newlines()追加(3箇所) - 2. `match_expr.rs`: is_object_literal()関数を改行対応(lookahead改良) +## 📝 Update (2025-09-24) ✅ 改行処理Phase 0 & Phase 1基本実装完了! +- ✅ **Phase 0 Quick Fix完了!** たった5箇所の修正で複数行オブジェクトリテラル完全動作 + - `primary.rs`: COLON前後とCOMMA判定前にskip_newlines()追加(3箇所) + - `match_expr.rs`: is_object_literal()関数を改行対応(lookahead改良) - **必須環境変数**: `NYASH_SYNTAX_SUGAR_LEVEL=full`(IDENTIFIERキー使用のため) - **テスト結果**: 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行)- 実験的実装 + - **主要機能**: + - NewlineMode(Stmt/Expr)による文脈認識改行処理 + - ブレース/パーレン/ブラケット深度の自動追跡 + - 行継続判定(演算子・カンマ等) + - with_expr_mode/with_stmt_mode によるモード一時切り替え + - **ビルド成功**: warning のみでエラーなし - 🎯 **セミコロンモード確認完了!** `NYASH_PARSER_ALLOW_SEMICOLON=1`で動作確認 - - セミコロンと改行を自由に混在可能 - - JavaScript/Go風の書き方も完全サポート - 📚 **改行処理戦略ドキュメント完成**: [newline-handling-strategy.md](docs/development/strategies/newline-handling-strategy.md) - - 3段階実装計画(Phase 0: Quick Fix ✅, Phase 1: TokenCursor, Phase 2: LASI) - - ChatGPT Pro提案のTokenCursorサンプルコード含む -- 🚀 **次の実装**: Phase 1 TokenCursor導入で改行処理を根本解決へ +- 🚀 **次の実装**: Phase 2 LASI前処理でトークン正規化(Phase 15後) ## 📝 Update (2025-09-23) ✅ フェーズS実装完了!break制御フロー根治開始 - ✅ **フェーズS完了!** PHI incoming修正+終端ガード徹底→重複処理4箇所統一 diff --git a/src/parser/cursor.rs b/src/parser/cursor.rs new file mode 100644 index 00000000..80dad2f5 --- /dev/null +++ b/src/parser/cursor.rs @@ -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(&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(&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)); + }); + } +} \ No newline at end of file diff --git a/src/parser/expr_cursor.rs b/src/parser/expr_cursor.rs new file mode 100644 index 00000000..2ab43d87 --- /dev/null +++ b/src/parser/expr_cursor.rs @@ -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 { + // 式モードで実行(改行を自動的にスキップ) + cursor.with_expr_mode(|c| { + Self::parse_or_expr(c) + }) + } + + /// OR式をパース + fn parse_or_expr(cursor: &mut TokenCursor) -> Result { + 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 { + 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 { + 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 { + 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 { + 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 { + match &cursor.current().token_type { + TokenType::PLUS => Some(BinaryOperator::Add), + TokenType::MINUS => Some(BinaryOperator::Subtract), + _ => None, + } + } + + /// 乗算式をパース + fn parse_multiplicative_expr(cursor: &mut TokenCursor) -> Result { + 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 { + 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 { + 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 { + 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(), + }) + } +} \ No newline at end of file diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6f403579..9b62f16a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18,9 +18,11 @@ // サブモジュール宣言 mod common; +mod cursor; // TokenCursor: 改行処理を一元管理 mod declarations; pub mod entry_sugar; // helper to parse with sugar level mod expr; +mod expr_cursor; // TokenCursorを使用した式パーサー(実験的) mod expressions; mod items; mod statements;