Phase 21.3 WIP: Hako Source Checker improvements

## 🎯 Checker/Analyzer拡張

###  実装追加
- テストフレームワーク追加(tools/hako_check/tests/)
- ルール改善(HC003グローバルassign、HC040静的箱トップレベルassign)
- テストランナー(run_tests.sh)

### 🔧 Rust側修正
- AST utilities拡張(src/ast/utils.rs)
- MIR lowerers新設(src/mir/lowerers/)
- Parser制御フロー改善(src/parser/statements/control_flow.rs)
- Tokenizer識別子処理改善(src/tokenizer/lex_ident.rs)

### 📁 主要変更
- tools/hako_check/cli.hako - CLI改善
- tools/hako_check/hako_source_checker.hako - Checker core更新
- tools/hako_check/tests/ - NEW (テストケース追加)
- tools/hako_check/run_tests.sh - NEW (テストランナー)
- src/mir/lowerers/ - NEW (MIR lowering utilities)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-07 21:04:01 +09:00
parent b8fbbafc0c
commit 86489ffe43
20 changed files with 547 additions and 165 deletions

View File

@ -17,8 +17,15 @@ use crate::tokenizer::TokenType;
impl NyashParser {
/// Parse control flow statement dispatch
pub(super) fn parse_control_flow_statement(&mut self) -> Result<ASTNode, ParseError> {
let stage3 = Self::is_stage3_enabled();
match &self.current_token().token_type {
TokenType::IF => self.parse_if(),
// Stage-3: while
TokenType::WHILE if stage3 => self.parse_while_stage3(),
// Stage-3: for-range (stubbed to clear error path; implement next)
TokenType::FOR if stage3 => self.parse_for_range_stage3(),
// Legacy loop
TokenType::LOOP => self.parse_loop(),
TokenType::BREAK => self.parse_break(),
TokenType::CONTINUE => self.parse_continue(),
@ -72,7 +79,7 @@ impl NyashParser {
})
}
/// Parse loop statement
/// Parse loop statement (legacy `loop`).
pub(super) fn parse_loop(&mut self) -> Result<ASTNode, ParseError> {
if super::helpers::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
@ -106,6 +113,97 @@ impl NyashParser {
})
}
/// Stage-3: while <cond> { body }
fn parse_while_stage3(&mut self) -> Result<ASTNode, ParseError> {
// Normalize cursor at statement start (skip leading newlines etc.)
if super::helpers::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
// consume 'while'
self.advance();
// condition expression (no parentheses required in MVP)
let condition = Box::new(self.parse_expression()?);
// body block
let body = self.parse_block_statements()?;
Ok(ASTNode::While {
condition,
body,
span: Span::unknown(),
})
}
/// Stage-3: for-range parsing helper (currently unused).
fn parse_for_range_stage3(&mut self) -> Result<ASTNode, ParseError> {
// Normalize cursor at statement start
if super::helpers::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
// consume 'for'
self.advance();
// expect identifier
let var_name = match &self.current_token().token_type {
TokenType::IDENTIFIER(s) => {
let n = s.clone();
self.advance();
n
}
other => {
return Err(ParseError::UnexpectedToken {
found: other.clone(),
expected: "identifier".to_string(),
line: self.current_token().line,
})
}
};
// expect 'in'
if !self.match_token(&TokenType::IN) {
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "in".to_string(),
line: self.current_token().line,
});
}
self.advance();
// start expr
let start = Box::new(self.parse_expression()?);
// expect RANGE ('..')
self.consume(TokenType::RANGE)?;
// end expr
let end = Box::new(self.parse_expression()?);
// body
let body = self.parse_block_statements()?;
Ok(ASTNode::ForRange {
var_name,
start,
end,
body,
span: Span::unknown(),
})
}
/// Helper: env-gated Stage-3 enable check.
fn is_stage3_enabled() -> bool {
let on = |key: &str| {
std::env::var(key)
.ok()
.map(|v| {
let lv = v.to_ascii_lowercase();
lv == "1" || lv == "true" || lv == "on"
})
.unwrap_or(false)
};
on("NYASH_PARSER_STAGE3") || on("HAKO_PARSER_STAGE3")
}
/// Parse break statement
pub(super) fn parse_break(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'break'
@ -144,4 +242,4 @@ impl NyashParser {
span: Span::unknown(),
})
}
}
}