/*! * Nyash Parser - Rust Implementation * * Python版nyashc_v4.pyのNyashParserをRustで完全再実装 * Token列をAST (Abstract Syntax Tree) に変換 * * モジュール構造: * - common.rs: 共通ユーティリティとトレイト (ParserUtils) * - expressions.rs: 式パーサー (parse_expression, parse_or, parse_and等) * - statements.rs: 文パーサー (parse_statement, parse_if, parse_loop等) * - declarations/: Box宣言パーサー (box_definition, static_box, dependency_helpers) * - items/: トップレベル宣言 (global_vars, functions, static_items) * * 2025-08-16: 大規模リファクタリング完了 * - 1530行 → 227行 (85%削減) * - 機能ごとにモジュール分離で保守性向上 */ // サブモジュール宣言 mod common; mod cursor; // TokenCursor: 改行処理を一元管理 mod declarations; // depth_tracking.rs was a legacy depth counter for Smart advance. // Phase 15.5: removed in favor of TokenCursor-centric newline handling. pub mod entry_sugar; // helper to parse with sugar level mod expr; mod expr_cursor; // TokenCursorを使用した式パーサー(実験的) mod expressions; mod items; mod statements; // Now uses modular structure in statements/ pub mod sugar; // Phase 12.7-B: desugar pass (basic) pub mod sugar_gate; // thread-local gate for sugar parsing (tests/docs) // mod errors; use common::ParserUtils; use crate::ast::{ASTNode, Span}; use crate::tokenizer::{Token, TokenType, TokenizeError}; use thiserror::Error; #[inline] fn is_sugar_enabled() -> bool { crate::parser::sugar_gate::is_enabled() } // ===== 🔥 Debug Macros ===== /// Infinite loop detection macro - must be called in every loop that advances tokens /// Prevents parser from hanging due to token consumption bugs /// Uses parser's debug_fuel field for centralized fuel management #[macro_export] macro_rules! must_advance { ($parser:expr, $fuel:expr, $location:literal) => { // デバッグ燃料がSomeの場合のみ制限チェック if let Some(ref mut limit) = $parser.debug_fuel { if *limit == 0 { eprintln!("🚨 PARSER INFINITE LOOP DETECTED at {}", $location); eprintln!( "🔍 Current token: {:?} at line {}", $parser.current_token().token_type, $parser.current_token().line ); eprintln!( "🔍 Parser position: {}/{}", $parser.current, $parser.tokens.len() ); return Err($crate::parser::ParseError::InfiniteLoop { location: $location.to_string(), token: $parser.current_token().token_type.clone(), line: $parser.current_token().line, }); } *limit -= 1; } // None の場合は無制限なのでチェックしない }; } /// Initialize debug fuel for loop monitoring #[macro_export] macro_rules! debug_fuel { () => { 100_000 // Default: 100k iterations should be enough for any reasonable program }; } // Two-phase parser structures are no longer needed - simplified to direct parsing /// パースエラー #[derive(Error, Debug)] pub enum ParseError { #[error("Unexpected token {found:?}, expected {expected} at line {line}")] UnexpectedToken { found: TokenType, expected: String, line: usize, }, #[error("Unexpected end of file")] UnexpectedEOF, #[error("Invalid expression at line {line}")] InvalidExpression { line: usize }, #[error("Invalid statement at line {line}")] InvalidStatement { line: usize }, #[error("Unsupported identifier '{name}' at line {line}")] UnsupportedIdentifier { name: String, line: usize }, #[error("Circular dependency detected between static boxes: {cycle}")] CircularDependency { cycle: String }, #[error("🚨 Infinite loop detected in parser at {location} - token: {token:?} at line {line}")] InfiniteLoop { location: String, token: TokenType, line: usize, }, #[error("🔥 Transparency system removed: {suggestion} at line {line}")] TransparencySystemRemoved { suggestion: String, line: usize }, #[error( "Unsupported namespace '{name}' at line {line}. Only 'nyashstd' is supported in Phase 0." )] UnsupportedNamespace { name: String, line: usize }, #[error("Expected identifier at line {line}")] ExpectedIdentifier { line: usize }, #[error("Tokenize error: {0}")] TokenizeError(#[from] TokenizeError), } /// Nyashパーサー - トークン列をASTに変換 pub struct NyashParser { pub(super) tokens: Vec, pub(super) current: usize, /// 🔥 Static box依存関係追跡(循環依存検出用) pub(super) static_box_dependencies: std::collections::HashMap>, /// 🔥 デバッグ燃料:無限ループ検出用制限値 (None = 無制限) pub(super) debug_fuel: Option, } // ParserUtils trait implementation now lives here (legacy depth tracking removed) impl NyashParser { /// 新しいパーサーを作成 pub fn new(tokens: Vec) -> Self { Self { tokens, current: 0, static_box_dependencies: std::collections::HashMap::new(), debug_fuel: Some(100_000), // デフォルト値 } } /// 文字列からパース (トークナイズ + パース) /// Note: Reads parser_stage3_enabled() (NYASH_FEATURES=stage3 or legacy env) for using-chain parsing pub fn parse_from_string(input: impl Into) -> Result { // Ensure Stage-3 features are enabled when parsing using-chain files // when parent requested Stage-3 parsing via NYASH_FEATURES/legacy env Self::parse_from_string_with_fuel(input, Some(100_000)) } /// 文字列からパース (デバッグ燃料指定版) /// fuel: Some(n) = n回まで、None = 無制限 pub fn parse_from_string_with_fuel( input: impl Into, fuel: Option, ) -> Result { // Normalize logical operators '||'/'&&' to 'or'/'and' before tokenization (outside strings/comments) fn normalize_logical_ops(src: &str) -> String { let mut out = String::with_capacity(src.len()); let mut it = src.chars().peekable(); let mut in_str = false; let mut in_line = false; let mut in_block = false; while let Some(c) = it.next() { if in_line { out.push(c); if c == '\n' { in_line = false; } continue; } if in_block { out.push(c); if c == '*' && matches!(it.peek(), Some('/')) { out.push('/'); it.next(); in_block = false; } continue; } if in_str { out.push(c); if c == '\\' { if let Some(nc) = it.next() { out.push(nc); } continue; } if c == '"' { in_str = false; } continue; } match c { '"' => { in_str = true; out.push(c); } '/' => match it.peek() { Some('/') => { out.push('/'); out.push('/'); it.next(); in_line = true; } Some('*') => { out.push('/'); out.push('*'); it.next(); in_block = true; } _ => out.push('/'), }, '#' => { in_line = true; out.push('#'); } '|' => { if matches!(it.peek(), Some('|')) { out.push_str(" or "); it.next(); } else if matches!(it.peek(), Some('>')) { out.push('|'); out.push('>'); it.next(); } else { out.push('|'); } } '&' => { if matches!(it.peek(), Some('&')) { out.push_str(" and "); it.next(); } else { out.push('&'); } } _ => out.push(c), } } out } let input_s: String = input.into(); let pre = normalize_logical_ops(&input_s); let mut tokenizer = crate::tokenizer::NyashTokenizer::new(pre); let tokens = tokenizer.tokenize()?; for tok in &tokens { if let TokenType::IDENTIFIER(name) = &tok.token_type { if name == "self" { return Err(ParseError::UnsupportedIdentifier { name: name.clone(), line: tok.line, }); } } } let mut parser = Self::new(tokens); parser.debug_fuel = fuel; let result = parser.parse(); result } /// パース実行 - Program ASTを返す pub fn parse(&mut self) -> Result { self.parse_program() } // ===== パース関数群 ===== /// プログラム全体をパース fn parse_program(&mut self) -> Result { let mut statements = Vec::new(); let mut _statement_count = 0; let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON") .ok() .map(|v| { let lv = v.to_ascii_lowercase(); !(lv == "0" || lv == "false" || lv == "off") }) .unwrap_or(true); while !self.is_at_end() { // EOF tokenはスキップ if matches!(self.current_token().token_type, TokenType::EOF) { break; } // NEWLINE tokenはスキップ(文の区切りとして使用) if matches!(self.current_token().token_type, TokenType::NEWLINE) || (allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON)) { self.advance(); continue; } let statement = self.parse_statement()?; statements.push(statement); _statement_count += 1; } // 🔥 すべてのstatic box解析後に循環依存検出 self.check_circular_dependencies()?; Ok(ASTNode::Program { statements, span: Span::unknown(), }) } // Statement parsing methods are now in statements.rs module /// 代入文または関数呼び出しをパース fn parse_assignment_or_function_call(&mut self) -> Result { // まず左辺を式としてパース let expr = self.parse_expression()?; // 次のトークンが = または 複合代入演算子 なら代入文 if self.match_token(&TokenType::ASSIGN) { self.advance(); // consume '=' let value = Box::new(self.parse_expression()?); // 左辺が代入可能な形式かチェック match &expr { ASTNode::Variable { .. } | ASTNode::FieldAccess { .. } | ASTNode::Index { .. } => { Ok(ASTNode::Assignment { target: Box::new(expr), value, span: Span::unknown(), }) } _ => { let line = self.current_token().line; Err(ParseError::InvalidStatement { line }) } } } else if self.match_token(&TokenType::PlusAssign) || self.match_token(&TokenType::MinusAssign) || self.match_token(&TokenType::MulAssign) || self.match_token(&TokenType::DivAssign) { if !is_sugar_enabled() { let line = self.current_token().line; return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full for '+=' and friends" .to_string(), line, }); } // determine operator let op = match &self.current_token().token_type { TokenType::PlusAssign => crate::ast::BinaryOperator::Add, TokenType::MinusAssign => crate::ast::BinaryOperator::Subtract, TokenType::MulAssign => crate::ast::BinaryOperator::Multiply, TokenType::DivAssign => crate::ast::BinaryOperator::Divide, _ => unreachable!(), }; self.advance(); // consume 'op=' let rhs = self.parse_expression()?; // 左辺が代入可能な形式かチェック match &expr { ASTNode::Variable { .. } | ASTNode::FieldAccess { .. } => { let left_clone = expr.clone(); let value = ASTNode::BinaryOp { operator: op, left: Box::new(left_clone), right: Box::new(rhs), span: Span::unknown(), }; Ok(ASTNode::Assignment { target: Box::new(expr), value: Box::new(value), span: Span::unknown(), }) } _ => { let line = self.current_token().line; Err(ParseError::InvalidStatement { line }) } } } else { // 代入文でなければ式文として返す Ok(expr) } } // Expression parsing methods are now in expressions.rs module // Utility methods are now in common.rs module via ParserUtils trait // Item parsing methods are now in items.rs module // ===== 🔥 Static Box循環依存検出 ===== } // ---- Minimal ParserUtils impl (depth-less; TokenCursor handles newline policy) ---- impl common::ParserUtils for NyashParser { fn tokens(&self) -> &Vec { &self.tokens } fn current(&self) -> usize { self.current } fn current_mut(&mut self) -> &mut usize { &mut self.current } fn update_depth_before_advance(&mut self) { /* no-op (legacy removed) */ } fn update_depth_after_advance(&mut self) { /* no-op (legacy removed) */ } }