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:
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user