diff --git a/docs/reference/ir/ast-json-v0.md b/docs/reference/ir/ast-json-v0.md index 0e361a3b..af967d44 100644 --- a/docs/reference/ir/ast-json-v0.md +++ b/docs/reference/ir/ast-json-v0.md @@ -25,6 +25,7 @@ Kinds (subset for Phase 2+) - FunctionCall: { kind: "FunctionCall", name: string, arguments: [Node] } - Array: { kind: "Array", elements: [Node] } - Map: { kind: "Map", entries: [{k: string, v: Node}] } +- Local: { kind: "Local", variables: [string], inits: [Node|null] } LiteralValue - { type: "string", value: string } diff --git a/src/macro/ast_json.rs b/src/macro/ast_json.rs index c90ff0c3..d5070b39 100644 --- a/src/macro/ast_json.rs +++ b/src/macro/ast_json.rs @@ -27,6 +27,11 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "target": ast_to_json(&target), "value": ast_to_json(&value), }), + ASTNode::Local { variables, initial_values, .. } => json!({ + "kind": "Local", + "variables": variables, + "inits": initial_values.into_iter().map(|opt| opt.map(|v| ast_to_json(&v))).collect::>() + }), ASTNode::If { condition, then_body, else_body, .. } => json!({ "kind": "If", "condition": ast_to_json(&condition), @@ -94,6 +99,13 @@ pub fn json_to_ast(v: &Value) -> Option { "Break" => ASTNode::Break { span: Span::unknown() }, "Continue" => ASTNode::Continue { span: Span::unknown() }, "Assignment" => ASTNode::Assignment { target: Box::new(json_to_ast(v.get("target")?)?), value: Box::new(json_to_ast(v.get("value")?)?), span: Span::unknown() }, + "Local" => { + let vars = v.get("variables")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect(); + let inits = v.get("inits")?.as_array()?.iter().map(|initv| { + if initv.is_null() { None } else { json_to_ast(initv).map(Box::new) } + }).collect(); + ASTNode::Local { variables: vars, initial_values: inits, span: Span::unknown() } + }, "If" => ASTNode::If { condition: Box::new(json_to_ast(v.get("condition")?)?), then_body: v.get("then")?.as_array()?.iter().filter_map(json_to_ast).collect::>(), else_body: v.get("else").and_then(|a| a.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::>())), span: Span::unknown() }, "FunctionDeclaration" => ASTNode::FunctionDeclaration { name: v.get("name")?.as_str()?.to_string(), diff --git a/src/macro/macro_box_ny.rs b/src/macro/macro_box_ny.rs index 354c401e..42338e4b 100644 --- a/src/macro/macro_box_ny.rs +++ b/src/macro/macro_box_ny.rs @@ -175,7 +175,7 @@ fn expand_indicates_uppercase(body: &Vec, params: &Vec) -> bool } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MacroBehavior { Identity, Uppercase, ArrayPrependZero, MapInsertTag } +pub enum MacroBehavior { Identity, Uppercase, ArrayPrependZero, MapInsertTag, LoopNormalize } pub fn analyze_macro_file(path: &str) -> MacroBehavior { let src = match std::fs::read_to_string(path) { Ok(s) => s, Err(_) => return MacroBehavior::Identity }; @@ -241,6 +241,18 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior { if let ASTNode::Program { statements, .. } = ast { for st in statements { if let ASTNode::BoxDeclaration { name: _, methods, .. } = st { + // Detect LoopNormalize by name() returning a specific string + if let Some(ASTNode::FunctionDeclaration { name: mname, body, .. }) = methods.get("name") { + if mname == "name" { + if body.len() == 1 { + if let ASTNode::Return { value: Some(v), .. } = &body[0] { + if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v { + if s == "LoopNormalize" { return MacroBehavior::LoopNormalize; } + } + } + } + } + } if let Some(ASTNode::FunctionDeclaration { name: mname, body, params, .. }) = methods.get("expand") { if mname == "expand" { if expand_indicates_uppercase(body, params) { diff --git a/src/runner/modes/macro_child.rs b/src/runner/modes/macro_child.rs index 10cf2adc..0c14ff92 100644 --- a/src/runner/modes/macro_child.rs +++ b/src/runner/modes/macro_child.rs @@ -95,6 +95,10 @@ pub fn run_macro_child(macro_file: &str) { } crate::r#macro::macro_box_ny::MacroBehavior::ArrayPrependZero => transform_array_prepend_zero(&ast), crate::r#macro::macro_box_ny::MacroBehavior::MapInsertTag => transform_map_insert_tag(&ast), + crate::r#macro::macro_box_ny::MacroBehavior::LoopNormalize => { + // MVP: identity (future: normalize Loop into carrier-based form) + ast.clone() + } }; let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast); println!("{}", out_json.to_string());