refactor(joinir): Phase 106 Pattern2 step boxes

This commit is contained in:
nyash-codex
2025-12-17 22:01:19 +09:00
parent 712e1ad755
commit 5f4d8ba112
11 changed files with 733 additions and 410 deletions

View File

@ -12,6 +12,7 @@
use super::PolicyDecision;
use crate::ast::{ASTNode, BinaryOperator, Span, UnaryOperator};
#[derive(Debug, Clone)]
pub(crate) struct LoopTrueReadDigitsPolicyResult {
pub break_condition_node: ASTNode,
pub allowed_ch_var: String,
@ -59,3 +60,137 @@ pub(crate) fn classify_loop_true_read_digits(
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
fn span() -> Span {
Span::unknown()
}
fn bool_lit(v: bool) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Bool(v),
span: span(),
}
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: span(),
}
}
fn str_lit(s: &str) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::String(s.to_string()),
span: span(),
}
}
fn int_lit(n: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(n),
span: span(),
}
}
fn eq(left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
fn add(left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
fn or(left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
fn not(node: ASTNode) -> ASTNode {
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(node),
span: span(),
}
}
fn digit_chain(var_name: &str, digits: &[&str]) -> ASTNode {
let mut it = digits.iter();
let first = it
.next()
.expect("digits must be non-empty");
let mut acc = eq(var(var_name), str_lit(first));
for d in it {
acc = or(acc, eq(var(var_name), str_lit(d)));
}
acc
}
#[test]
fn read_digits_loop_true_policy_returns_break_when_true_and_allowlist() {
// loop(true) {
// if ch == "" { break }
// if is_digit(ch) { out = out + ch; i = i + 1 } else { break }
// }
let condition = bool_lit(true);
let eos_cond = eq(var("ch"), str_lit(""));
let digit_cond = digit_chain(
"ch",
&["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
);
let body = vec![
ASTNode::If {
condition: Box::new(eos_cond.clone()),
then_body: vec![ASTNode::Break { span: span() }],
else_body: None,
span: span(),
},
ASTNode::If {
condition: Box::new(digit_cond.clone()),
then_body: vec![
ASTNode::Assignment {
target: Box::new(var("out")),
value: Box::new(add(var("out"), var("ch"))),
span: span(),
},
ASTNode::Assignment {
target: Box::new(var("i")),
value: Box::new(add(var("i"), int_lit(1))),
span: span(),
},
],
else_body: Some(vec![ASTNode::Break { span: span() }]),
span: span(),
},
];
let decision = classify_loop_true_read_digits(&condition, &body);
let result = match decision {
PolicyDecision::Use(v) => v,
other => panic!("expected PolicyDecision::Use, got {:?}", other),
};
assert_eq!(result.allowed_ch_var, "ch");
assert_eq!(result.break_condition_node, or(eos_cond, not(digit_cond)));
}
}