Files
hakorune/src/macro/ast_json.rs
tomoaki 9227673ef7 feat(phase285w): Implement weak x unary operator syntax
Phase 285W-Syntax-0: Migrate weak reference syntax from function call
to unary operator for consistency and clarity.

**Changes**:
- Parser: Add UnaryOperator::Weak variant and parse_unary() handling
- MIR: Lower UnaryOp::Weak to emit_weak_new() (reuses existing path)
- AST: Add Weak to UnaryOperator enum + Display/JSON support
- Tests: Migrate 8 files from `weak(x)` to `weak x` syntax
  - 7 .hako test files updated
  - 1 smoke test shell script updated
- Cleanup: Remove obsolete weak(x) parser/MIR special cases
- Docs: Update Phase 285 README

**Syntax Change**:
- Old: `local w = weak(x)`  (function call)
- New: `local w = weak x`   (unary operator)

**Validation**: All migrated phase285* smoke tests pass (4/4 relevant)

**SSOT**: docs/reference/language/lifecycle.md

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 17:21:21 +09:00

628 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
use serde_json::{json, Value};
pub fn ast_to_json(ast: &ASTNode) -> Value {
match ast.clone() {
ASTNode::Program { statements, .. } => json!({
"kind": "Program",
"statements": statements.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
}),
// Phase 54: Loop with JoinIR-compatible fields
ASTNode::Loop {
condition, body, ..
} => json!({
"kind": "Loop",
"type": "Loop", // JoinIR Frontend expects "type"
"condition": ast_to_json(&condition),
"cond": ast_to_json(&condition), // JoinIR expects "cond"
"body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
}),
// Phase 54: Print with JoinIR-compatible fields
ASTNode::Print { expression, .. } => json!({
"kind": "Print",
"type": "Print", // JoinIR Frontend expects "type"
"expression": ast_to_json(&expression),
"expr": ast_to_json(&expression), // JoinIR expects "expr"
}),
// Phase 54: Return with JoinIR-compatible fields
ASTNode::Return { value, .. } => json!({
"kind": "Return",
"type": "Return", // JoinIR Frontend expects "type"
"value": value.as_ref().map(|v| ast_to_json(v)),
}),
// Phase 56: Break with JoinIR-compatible type field
ASTNode::Break { .. } => json!({
"kind": "Break",
"type": "Break" // JoinIR Frontend expects "type"
}),
// Phase 56: Continue with JoinIR-compatible type field
ASTNode::Continue { .. } => json!({
"kind": "Continue",
"type": "Continue" // JoinIR Frontend expects "type"
}),
// Phase 54: Assignment with JoinIR-compatible fields
ASTNode::Assignment { target, value, .. } => {
// Extract variable name if target is a simple Variable
let target_str = match target.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => "complex".to_string(), // FieldAccess or other complex target
};
json!({
"kind": "Assignment",
"type": "Assignment", // JoinIR Frontend expects "type"
"target": target_str, // JoinIR expects string variable name
"lhs": ast_to_json(&target), // Keep full AST for complex cases
"value": ast_to_json(&value),
"expr": ast_to_json(&value), // JoinIR expects "expr"
})
}
// Phase 54: Local with JoinIR-compatible fields
ASTNode::Local {
variables,
initial_values,
..
} => {
// For single-variable declarations, add "name" and "expr" for JoinIR compatibility
let (name, expr) = if variables.len() == 1 {
let n = variables[0].clone();
let e = initial_values
.get(0)
.and_then(|opt| opt.as_ref())
.map(|v| ast_to_json(v));
(Some(n), e)
} else {
(None, None)
};
let inits: Vec<_> = initial_values
.into_iter()
.map(|opt| opt.map(|v| ast_to_json(&v)))
.collect();
json!({
"kind": "Local",
"type": "Local", // JoinIR Frontend expects "type"
"name": name, // Single variable name for JoinIR (null if multiple)
"expr": expr, // Single variable init for JoinIR (null if multiple)
"variables": variables,
"inits": inits
})
}
// Phase 54: If with JoinIR-compatible fields
ASTNode::If {
condition,
then_body,
else_body,
..
} => json!({
"kind": "If",
"type": "If", // JoinIR Frontend expects "type"
"condition": ast_to_json(&condition),
"cond": ast_to_json(&condition), // JoinIR expects "cond"
"then": then_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
"else": else_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()),
}),
ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
..
} => json!({
"kind": "TryCatch",
"try": try_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
"catch": catch_clauses.into_iter().map(|cc| json!({
"type": cc.exception_type,
"var": cc.variable_name,
"body": cc.body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
})).collect::<Vec<_>>(),
"cleanup": finally_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>())
}),
ASTNode::FunctionDeclaration {
name,
params,
body,
is_static,
is_override,
..
} => json!({
"kind": "FunctionDeclaration",
"name": name,
"params": params,
"body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
"static": is_static,
"override": is_override,
}),
// Phase 52: Variable → Var ードJoinIR Frontend 互換)
ASTNode::Variable { name, .. } => json!({
"kind": "Variable",
"type": "Var", // JoinIR Frontend expects "type": "Var"
"name": name
}),
// Phase 52: Literal → Int/Bool/String ードJoinIR Frontend 互換)
ASTNode::Literal { value, .. } => literal_to_joinir_json(&value),
// Phase 52: BinaryOp → Binary/Compare ードJoinIR Frontend 互換)
ASTNode::BinaryOp {
operator,
left,
right,
..
} => {
let op_str = bin_to_str(&operator);
// JoinIR Frontend distinguishes between Binary (arithmetic) and Compare
let type_str = if is_compare_op(&operator) {
"Compare"
} else {
"Binary"
};
json!({
"kind": "BinaryOp",
"type": type_str,
"op": op_str,
// JoinIR Frontend expects "lhs"/"rhs" not "left"/"right"
"lhs": ast_to_json(&left),
"rhs": ast_to_json(&right),
// Also keep "left"/"right" for backward compatibility
"left": ast_to_json(&left),
"right": ast_to_json(&right),
})
}
// Phase 56: UnaryOp → Unary ードJoinIR Frontend 互換)
ASTNode::UnaryOp {
operator, operand, ..
} => json!({
"kind": "UnaryOp",
"type": "Unary", // Phase 56: JoinIR Frontend expects "type" field
"op": un_to_str(&operator),
"operand": ast_to_json(&operand),
}),
// Phase 52: MethodCall → Method ードJoinIR Frontend 互換)
ASTNode::MethodCall {
object,
method,
arguments,
..
} => json!({
"kind": "MethodCall",
"type": "Method", // JoinIR Frontend expects "type": "Method"
// JoinIR Frontend expects "receiver" not "object"
"receiver": ast_to_json(&object),
"object": ast_to_json(&object), // Keep for backward compatibility
"method": method,
// JoinIR Frontend expects "args" not "arguments"
"args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(),
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>() // Keep for backward compatibility
}),
// Phase 56: FunctionCall with JoinIR-compatible type field
ASTNode::FunctionCall {
name, arguments, ..
} => json!({
"kind": "FunctionCall",
"type": "Call", // JoinIR Frontend expects "type": "Call"
"name": name,
"func": name.clone(), // JoinIR expects "func" for function name
"args": arguments.iter().map(|a| ast_to_json(a)).collect::<Vec<_>>(), // JoinIR expects "args"
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>() // Keep for backward compatibility
}),
// Phase 56: ArrayLiteral with JoinIR-compatible type field
ASTNode::ArrayLiteral { elements, .. } => json!({
"kind": "Array",
"type": "Array", // JoinIR Frontend expects "type"
"elements": elements.into_iter().map(|e| ast_to_json(&e)).collect::<Vec<_>>()
}),
// Phase 56: MapLiteral with JoinIR-compatible type field
ASTNode::MapLiteral { entries, .. } => json!({
"kind": "Map",
"type": "Map", // JoinIR Frontend expects "type"
"entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
}),
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,
..
} => json!({
"kind":"MatchExpr",
"scrutinee": ast_to_json(&scrutinee),
"arms": arms.into_iter().map(|(lit, body)| json!({
"literal": {
"kind": "Literal",
"value": lit_to_json(&lit)
},
"body": ast_to_json(&body)
})).collect::<Vec<_>>(),
"else": ast_to_json(&else_expr),
}),
// Phase 52: FieldAccess → Field ードJoinIR Frontend 互換)
ASTNode::FieldAccess { object, field, .. } => json!({
"kind": "FieldAccess",
"type": "Field", // JoinIR Frontend expects "type": "Field"
"object": ast_to_json(&object),
"field": field
}),
// Phase 52: Me → Var("me") ードJoinIR Frontend 互換)
ASTNode::Me { .. } => json!({
"kind": "Me",
"type": "Var", // JoinIR Frontend expects "type": "Var"
"name": "me"
}),
// Phase 52: New → NewBox ードJoinIR Frontend 互換)
ASTNode::New {
class, arguments, ..
} => json!({
"kind": "New",
"type": "NewBox", // JoinIR Frontend expects "type": "NewBox"
"box_name": class,
"args": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
}),
other => json!({"kind":"Unsupported","debug": format!("{:?}", other)}),
}
}
pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
let k = v.get("kind")?.as_str()?;
Some(match k {
"Program" => {
let stmts = v
.get("statements")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
ASTNode::Program {
statements: stmts,
span: Span::unknown(),
}
}
"Loop" => ASTNode::Loop {
condition: Box::new(json_to_ast(v.get("condition")?)?),
body: v
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>(),
span: Span::unknown(),
},
"Print" => ASTNode::Print {
expression: Box::new(json_to_ast(v.get("expression")?)?),
span: Span::unknown(),
},
"Return" => ASTNode::Return {
value: v.get("value").and_then(json_to_ast).map(Box::new),
span: Span::unknown(),
},
"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::<Vec<_>>(),
else_body: v.get("else").and_then(|a| {
a.as_array()
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
}),
span: Span::unknown(),
},
"FunctionDeclaration" => ASTNode::FunctionDeclaration {
name: v.get("name")?.as_str()?.to_string(),
params: v
.get("params")?
.as_array()?
.iter()
.filter_map(|s| s.as_str().map(|x| x.to_string()))
.collect(),
body: v
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
is_static: v.get("static").and_then(|b| b.as_bool()).unwrap_or(false),
is_override: v.get("override").and_then(|b| b.as_bool()).unwrap_or(false),
span: Span::unknown(),
},
"Variable" => ASTNode::Variable {
name: v.get("name")?.as_str()?.to_string(),
span: Span::unknown(),
},
"Literal" => ASTNode::Literal {
value: json_to_lit(v.get("value")?)?,
span: Span::unknown(),
},
"BinaryOp" => ASTNode::BinaryOp {
operator: str_to_bin(v.get("op")?.as_str()?)?,
left: Box::new(json_to_ast(v.get("left")?)?),
right: Box::new(json_to_ast(v.get("right")?)?),
span: Span::unknown(),
},
"UnaryOp" => ASTNode::UnaryOp {
operator: str_to_un(v.get("op")?.as_str()?)?,
operand: Box::new(json_to_ast(v.get("operand")?)?),
span: Span::unknown(),
},
"MethodCall" => ASTNode::MethodCall {
object: Box::new(json_to_ast(v.get("object")?)?),
method: v.get("method")?.as_str()?.to_string(),
arguments: v
.get("arguments")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"FunctionCall" => ASTNode::FunctionCall {
name: v.get("name")?.as_str()?.to_string(),
arguments: v
.get("arguments")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"Array" => ASTNode::ArrayLiteral {
elements: v
.get("elements")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"Map" => ASTNode::MapLiteral {
entries: v
.get("entries")?
.as_array()?
.iter()
.filter_map(|e| {
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
})
.collect(),
span: Span::unknown(),
},
"MatchExpr" => {
let scr = json_to_ast(v.get("scrutinee")?)?;
let arms_json = v.get("arms")?.as_array()?.iter();
let mut arms = Vec::new();
for arm_v in arms_json {
let lit_val = arm_v.get("literal")?.get("value")?;
let lit = json_to_lit(lit_val)?;
let body = json_to_ast(arm_v.get("body")?)?;
arms.push((lit, body));
}
let else_expr = json_to_ast(v.get("else")?)?;
ASTNode::MatchExpr {
scrutinee: Box::new(scr),
arms,
else_expr: Box::new(else_expr),
span: Span::unknown(),
}
}
"TryCatch" => {
let try_b = v
.get("try")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
let mut catches = Vec::new();
if let Some(arr) = v.get("catch").and_then(|x| x.as_array()) {
for c in arr.iter() {
let exc_t = match c.get("type") {
Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()),
_ => None,
};
let var = match c.get("var") {
Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()),
_ => None,
};
let body = c
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
catches.push(nyash_rust::ast::CatchClause {
exception_type: exc_t,
variable_name: var,
body,
span: Span::unknown(),
});
}
}
let cleanup = v.get("cleanup").and_then(|cl| {
cl.as_array()
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
});
ASTNode::TryCatch {
try_body: try_b,
catch_clauses: catches,
finally_body: cleanup,
span: Span::unknown(),
}
}
_ => return None,
})
}
fn lit_to_json(v: &LiteralValue) -> Value {
match v {
LiteralValue::String(s) => json!({"type":"string","value":s}),
LiteralValue::Integer(i) => json!({"type":"int","value":i}),
LiteralValue::Float(f) => json!({"type":"float","value":f}),
LiteralValue::Bool(b) => json!({"type":"bool","value":b}),
LiteralValue::Null => json!({"type":"null"}),
LiteralValue::Void => json!({"type":"void"}),
}
}
fn json_to_lit(v: &Value) -> Option<LiteralValue> {
let t = v.get("type")?.as_str()?;
Some(match t {
"string" => LiteralValue::String(v.get("value")?.as_str()?.to_string()),
"int" => LiteralValue::Integer(v.get("value")?.as_i64()?),
"float" => LiteralValue::Float(v.get("value")?.as_f64()?),
"bool" => LiteralValue::Bool(v.get("value")?.as_bool()?),
"null" => LiteralValue::Null,
"void" => LiteralValue::Void,
_ => return None,
})
}
fn bin_to_str(op: &BinaryOperator) -> &'static str {
match op {
BinaryOperator::Add => "+",
BinaryOperator::Subtract => "-",
BinaryOperator::Multiply => "*",
BinaryOperator::Divide => "/",
BinaryOperator::Modulo => "%",
BinaryOperator::BitAnd => "&",
BinaryOperator::BitOr => "|",
BinaryOperator::BitXor => "^",
BinaryOperator::Shl => "<<",
BinaryOperator::Shr => ">>",
BinaryOperator::Equal => "==",
BinaryOperator::NotEqual => "!=",
BinaryOperator::Less => "<",
BinaryOperator::Greater => ">",
BinaryOperator::LessEqual => "<=",
BinaryOperator::GreaterEqual => ">=",
BinaryOperator::And => "&&",
BinaryOperator::Or => "||",
}
}
fn str_to_bin(s: &str) -> Option<BinaryOperator> {
Some(match s {
"+" => BinaryOperator::Add,
"-" => BinaryOperator::Subtract,
"*" => BinaryOperator::Multiply,
"/" => BinaryOperator::Divide,
"%" => BinaryOperator::Modulo,
"&" => BinaryOperator::BitAnd,
"|" => BinaryOperator::BitOr,
"^" => BinaryOperator::BitXor,
"<<" => BinaryOperator::Shl,
">>" => BinaryOperator::Shr,
"==" => BinaryOperator::Equal,
"!=" => BinaryOperator::NotEqual,
"<" => BinaryOperator::Less,
">" => BinaryOperator::Greater,
"<=" => BinaryOperator::LessEqual,
">=" => BinaryOperator::GreaterEqual,
"&&" => BinaryOperator::And,
"||" => BinaryOperator::Or,
_ => return None,
})
}
fn un_to_str(op: &UnaryOperator) -> &'static str {
match op {
UnaryOperator::Minus => "-",
UnaryOperator::Not => "not",
UnaryOperator::BitNot => "~",
UnaryOperator::Weak => "weak", // Phase 285W-Syntax-0
}
}
fn str_to_un(s: &str) -> Option<UnaryOperator> {
Some(match s {
"-" => UnaryOperator::Minus,
"not" => UnaryOperator::Not,
"~" => UnaryOperator::BitNot,
"weak" => UnaryOperator::Weak, // Phase 285W-Syntax-0
_ => return None,
})
}
/// Phase 52: Check if a binary operator is a comparison operator
fn is_compare_op(op: &BinaryOperator) -> bool {
matches!(
op,
BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::Less
| BinaryOperator::Greater
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
)
}
/// Phase 52: Convert a literal value to JoinIR-compatible JSON format
///
/// JoinIR Frontend expects:
/// - Integer: {"type": "Int", "value": <number>}
/// - Boolean: {"type": "Bool", "value": <bool>}
/// - String: {"type": "String", "value": <string>}
fn literal_to_joinir_json(v: &LiteralValue) -> Value {
match v {
LiteralValue::Integer(i) => json!({
"kind": "Literal",
"type": "Int", // JoinIR Frontend expects "type": "Int"
"value": i
}),
LiteralValue::Bool(b) => json!({
"kind": "Literal",
"type": "Bool", // JoinIR Frontend expects "type": "Bool"
"value": b
}),
LiteralValue::String(s) => json!({
"kind": "Literal",
"type": "String",
"value": s
}),
LiteralValue::Float(f) => json!({
"kind": "Literal",
"type": "Float",
"value": f
}),
LiteralValue::Null => json!({
"kind": "Literal",
"type": "Null"
}),
LiteralValue::Void => json!({
"kind": "Literal",
"type": "Void"
}),
}
}