Files
hakorune/src/parser/mod.rs

357 lines
13 KiB
Rust
Raw Normal View History

/*!
* 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("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."
)]
feat(phase-9.75e): Complete using nyashstd standard library implementation 🎉 Phase 9.75e完了: using nyashstdシステム完全実装成功 ✅ 実装完了項目: - USING tokenizer integration: TokenType::USING token support - UsingStatement AST node: Complete using statement parsing - BuiltinStdlib infrastructure: Core standard library framework - Full interpreter integration: Complete namespace resolution - Module system integration: Fixed crate::stdlib import issues 🌟 動作確認済み標準ライブラリ機能: - string.create("text") → StringBox creation - string.upper(str) → Uppercase string conversion - integer.create(42) → IntegerBox creation - bool.create(true) → BoolBox creation - array.create() → Empty ArrayBox creation - console.log("message") → Console output 📋 実装ファイル: - src/tokenizer.rs: USING token support - src/ast.rs: UsingStatement AST node - src/parser/statements.rs: using statement parser - src/interpreter/statements.rs: using statement execution - src/interpreter/core.rs: stdlib namespace resolution - src/stdlib/mod.rs: Complete BuiltinStdlib implementation - src/lib.rs + src/main.rs: Module declaration integration 🎯 テスト成功: All nyashstd functions work perfectly with comprehensive test coverage. Local test file: local_tests/test_nyashstd.nyash Everything is Box哲学を維持しながらモダンな標準ライブラリアクセスを実現\! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 01:12:10 +09:00
UnsupportedNamespace { name: String, line: usize },
feat(phase-9.75e): Complete using nyashstd standard library implementation 🎉 Phase 9.75e完了: using nyashstdシステム完全実装成功 ✅ 実装完了項目: - USING tokenizer integration: TokenType::USING token support - UsingStatement AST node: Complete using statement parsing - BuiltinStdlib infrastructure: Core standard library framework - Full interpreter integration: Complete namespace resolution - Module system integration: Fixed crate::stdlib import issues 🌟 動作確認済み標準ライブラリ機能: - string.create("text") → StringBox creation - string.upper(str) → Uppercase string conversion - integer.create(42) → IntegerBox creation - bool.create(true) → BoolBox creation - array.create() → Empty ArrayBox creation - console.log("message") → Console output 📋 実装ファイル: - src/tokenizer.rs: USING token support - src/ast.rs: UsingStatement AST node - src/parser/statements.rs: using statement parser - src/interpreter/statements.rs: using statement execution - src/interpreter/core.rs: stdlib namespace resolution - src/stdlib/mod.rs: Complete BuiltinStdlib implementation - src/lib.rs + src/main.rs: Module declaration integration 🎯 テスト成功: All nyashstd functions work perfectly with comprehensive test coverage. Local test file: local_tests/test_nyashstd.nyash Everything is Box哲学を維持しながらモダンな標準ライブラリアクセスを実現\! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 01:12:10 +09:00
#[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<Token>,
pub(super) current: usize,
/// 🔥 Static box依存関係追跡循環依存検出用
pub(super) static_box_dependencies:
std::collections::HashMap<String, std::collections::HashSet<String>>,
/// 🔥 デバッグ燃料:無限ループ検出用制限値 (None = 無制限)
pub(super) debug_fuel: Option<usize>,
}
// ParserUtils trait implementation now lives here (legacy depth tracking removed)
impl NyashParser {
/// 新しいパーサーを作成
pub fn new(tokens: Vec<Token>) -> Self {
Self {
tokens,
current: 0,
static_box_dependencies: std::collections::HashMap::new(),
debug_fuel: Some(100_000), // デフォルト値
}
}
/// 文字列からパース (トークナイズ + パース)
pub fn parse_from_string(input: impl Into<String>) -> Result<ASTNode, ParseError> {
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<String>,
fuel: Option<usize>,
) -> Result<ASTNode, ParseError> {
// 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()?;
let mut parser = Self::new(tokens);
parser.debug_fuel = fuel;
🎨 feat: EguiBox GUI開発基盤完成 + パーサー無限ループバグ修正 ## 🚀 主要機能追加 ### EguiBox - GUI開発基盤 - Windows版GUIメモ帳アプリ (simple_notepad.rs, nyash_notepad_jp.rs) - 日本語フォント対応 (NotoSansJP-VariableFont_wght.ttf) - BMPアイコン表示システム (c_drive_icon.bmp) - Windowsエクスプローラー風アプリ (nyash_explorer.rs) - アイコン抽出システム (test_icon_extraction.rs) ### ビジュアルプログラミング準備 - NyashFlow プロジェクト設計完成 (NYASHFLOW_PROJECT_HANDOVER.md) - ビジュアルノードプロトタイプ基盤 - WebAssembly対応準備 ## 🔧 重大バグ修正 ### パーサー無限ループ問題 (3引数メソッド呼び出し) - 原因: メソッドパラメータ解析ループの予約語処理不備 - 修正: src/parser/mod.rs - 非IDENTIFIERトークンのエラーハンドリング追加 - 効果: "from"等の予約語で適切なエラー報告、ハング→瞬時エラー ### MapBoxハング問題調査 - MapBox+3引数メソッド呼び出し組み合わせ問題特定 - バグレポート作成 (MAPBOX_HANG_BUG_REPORT.md) - 事前評価vs必要時評価の設計問題明確化 ## 🧹 コード品質向上 - box_methods.rs を8モジュールに機能分離 - 一時デバッグコード全削除 (eprintln\!, unsafe等) - 構文チェック通過確認済み ## 📝 ドキュメント整備 - CLAUDE.md にGUI開発セクション追加 - Gemini/ChatGPT先生相談ログ保存 (sessions/) - 段階的デバッグ手法確立 ## 🎯 次の目標 - must_advance\!マクロ実装 (無限ループ早期検出) - コマンド引数でデバッグ制御 (--debug-fuel) - MapBox問題の根本修正 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-10 07:54:03 +09:00
let result = parser.parse();
result
}
/// パース実行 - Program ASTを返す
pub fn parse(&mut self) -> Result<ASTNode, ParseError> {
self.parse_program()
}
// ===== パース関数群 =====
/// プログラム全体をパース
fn parse_program(&mut self) -> Result<ASTNode, ParseError> {
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<ASTNode, ParseError> {
// まず左辺を式としてパース
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<Token> { &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) */ }
}