use abyss_core::ast::{AST, ArtifactField, ArtifactMethodTarget, AssignmentOp, Type};
use abyss_core::format::format_ast;
fn arcana(value: i64) -> Box<AST> {
Box::new(AST::Arcana(value, None))
}
fn rune(value: &str) -> Box<AST> {
Box::new(AST::Rune(value.to_string(), None))
}
fn omen(value: bool) -> Box<AST> {
Box::new(AST::Omen(value, None))
}
fn var(name: &str) -> Box<AST> {
Box::new(AST::Var(name.to_string(), None))
}
fn abyss() -> Box<AST> {
Box::new(AST::Abyss(None))
}
#[test]
fn format_basic_expressions_and_assignments() {
let expr_stmt = AST::Statement(
Box::new(AST::Mul(
Box::new(AST::Add(arcana(1), arcana(2), None)),
arcana(3),
None,
)),
None,
);
assert_eq!(format_ast(&expr_stmt, 1), " (1 + 2) * 3;");
let logical = AST::LogicalNot(omen(false), None);
assert_eq!(format_ast(&logical, 0), "!hex");
let var_assign = AST::VarAssign {
name: "sigil".into(),
value: rune("alpha"),
var_type: Type::Rune,
is_morph: true,
line_info: None,
};
assert_eq!(
format_ast(&var_assign, 0),
"forge morph sigil: rune = \"alpha\""
);
let assign = AST::Assignment {
name: "sigil".into(),
value: var("base"),
op: AssignmentOp::PowAetherAssign,
line_info: None,
};
assert_eq!(format_ast(&assign, 0), "sigil **= base");
assert_eq!(format_ast(&AST::Arcana(7, None), 0), "7");
assert_eq!(format_ast(&AST::Aether(7.0, None), 0), "7.0");
assert_eq!(format_ast(&AST::Aether(7.25, None), 0), "7.25");
assert_eq!(format_ast(&AST::Rune("echo".into(), None), 0), "\"echo\"");
assert_eq!(format_ast(&AST::Omen(true, None), 0), "boon");
assert_eq!(format_ast(&AST::Abyss(None), 0), "abyss");
let reveal_core = AST::Reveal(var("sigil"), None);
assert_eq!(format_ast(&reveal_core, 0), "reveal sigil");
let reveal_abyss = AST::Reveal(abyss(), None);
assert_eq!(format_ast(&reveal_abyss, 0), "reveal");
let field_access = AST::FieldAccess {
target: var("relic"),
field: "core".into(),
line_info: None,
};
assert_eq!(format_ast(&field_access, 0), "relic.core");
let field_assignment = AST::FieldAssignment {
target: var("relic"),
field: "core".into(),
value: arcana(5),
line_info: None,
};
assert_eq!(format_ast(&field_assignment, 0), "relic.core = 5");
let index_assignment = AST::IndexAssignment {
target: var("sigils"),
index: arcana(2),
value: rune("beta"),
line_info: None,
};
assert_eq!(format_ast(&index_assignment, 0), "sigils[2] = \"beta\"");
let method_call = AST::MethodCall {
receiver: var("relic"),
method: "ignite".into(),
args: vec![*rune("spark")],
line_info: None,
};
assert_eq!(format_ast(&method_call, 0), "relic.ignite(\"spark\")");
}
#[test]
fn format_collections_and_literals() {
let list = AST::ListLiteral {
elements: vec![
AST::Arcana(1, None),
AST::Arcana(2, None),
AST::Arcana(3, None),
],
line_info: None,
};
assert_eq!(format_ast(&list, 0), "[1, 2, 3]");
let map = AST::MapLiteral {
entries: vec![
("name".into(), AST::Rune("abyss".into(), None)),
("value".into(), AST::Arcana(3, None)),
],
line_info: None,
};
assert_eq!(format_ast(&map, 0), "{\"name\": \"abyss\", \"value\": 3}");
let artifact_literal = AST::ArtifactLiteral {
type_name: "Relic".into(),
fields: vec![
("sigil".into(), AST::Rune("alpha".into(), None)),
("power".into(), AST::Arcana(9, None)),
],
line_info: None,
};
assert_eq!(
format_ast(&artifact_literal, 0),
"Relic { sigil: \"alpha\", power: 9 }"
);
let artifact_empty = AST::ArtifactLiteral {
type_name: "Relic".into(),
fields: vec![],
line_info: None,
};
assert_eq!(format_ast(&artifact_empty, 0), "Relic {}");
let artifact_def = AST::ArtifactDef {
name: "Relic".into(),
fields: vec![
ArtifactField {
name: "sigil".into(),
field_type: Type::Rune,
line_info: None,
},
ArtifactField {
name: "power".into(),
field_type: Type::Arcana,
line_info: None,
},
],
line_info: None,
};
let expected = concat!(
"artifact Relic {\n",
" sigil: rune;\n",
" power: arcana;\n",
"}"
);
assert_eq!(format_ast(&artifact_def, 0), expected);
let list_access = AST::IndexAccess {
target: var("sigils"),
index: arcana(0),
line_info: None,
};
assert_eq!(format_ast(&list_access, 0), "sigils[0]");
let func_call = AST::FuncCall {
name: "summon".into(),
args: vec![AST::Rune("echo".into(), None), AST::Arcana(1, None)],
line_info: None,
};
assert_eq!(format_ast(&func_call, 0), "summon(\"echo\", 1)");
}
#[test]
fn format_control_flow_and_functions() {
let block = AST::Block(
vec![AST::Statement(
Box::new(AST::Reveal(var("sigil"), None)),
None,
)],
None,
);
assert_eq!(format_ast(&block, 0), "{\n reveal sigil;\n}");
let oracle = AST::Oracle {
is_match: false,
conditionals: Vec::new(),
branches: vec![
AST::OracleBranch {
pattern: vec![AST::Arcana(1, None)],
guard: None,
body: Box::new(AST::Reveal(var("spark"), None)),
line_info: None,
},
AST::Comment("// fallback".into(), None),
AST::OracleBranch {
pattern: vec![AST::OracleDontCareItem(None)],
guard: None,
body: Box::new(AST::Reveal(Box::new(AST::Rune("wild".into(), None)), None)),
line_info: None,
},
AST::OracleBranch {
pattern: vec![],
guard: None,
body: Box::new(AST::Reveal(abyss(), None)),
line_info: None,
},
],
line_info: None,
};
let oracle_expected = concat!(
"oracle {\n",
" (1) => reveal spark\n",
" // fallback\n",
" (_) => reveal \"wild\"\n",
" _ => reveal\n",
"}"
);
assert_eq!(format_ast(&oracle, 0), oracle_expected);
let oracle_with_ward = AST::Oracle {
is_match: true,
conditionals: vec![abyss_core::ast::ConditionalAssignment {
variable: "__match_0".into(),
expression: var("count"),
line_info: None,
}],
branches: vec![
AST::OracleBranch {
pattern: vec![AST::Arcana(1, None)],
guard: Some(Box::new(AST::GreaterThan(var("count"), arcana(0), None))),
body: Box::new(AST::Reveal(Box::new(AST::Rune("ready".into(), None)), None)),
line_info: None,
},
AST::OracleBranch {
pattern: vec![AST::OracleDontCareItem(None)],
guard: None,
body: Box::new(AST::Reveal(Box::new(AST::Rune("idle".into(), None)), None)),
line_info: None,
},
],
line_info: None,
};
let oracle_with_ward_expected = concat!(
"oracle (count) {\n",
" (1) ward count > 0 => reveal \"ready\"\n",
" (_) => reveal \"idle\"\n",
"}"
);
assert_eq!(format_ast(&oracle_with_ward, 0), oracle_with_ward_expected);
let oracle_with_scroll_pattern = AST::Oracle {
is_match: true,
conditionals: vec![abyss_core::ast::ConditionalAssignment {
variable: "__match_0".into(),
expression: var("xs"),
line_info: None,
}],
branches: vec![
AST::OracleBranch {
pattern: vec![AST::OracleScrollPattern {
elements: vec![],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(Box::new(AST::Rune("empty".into(), None)), None)),
line_info: None,
},
AST::OracleBranch {
pattern: vec![AST::OracleScrollPattern {
elements: vec![
AST::Var("head".into(), None),
AST::OracleScrollRest {
name: Some("rest".into()),
line_info: None,
},
],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(var("head"), None)),
line_info: None,
},
],
line_info: None,
};
let oracle_with_scroll_pattern_expected = concat!(
"oracle (xs) {\n",
" [] => reveal \"empty\"\n",
" [head, ..rest] => reveal head\n",
"}"
);
assert_eq!(
format_ast(&oracle_with_scroll_pattern, 0),
oracle_with_scroll_pattern_expected
);
let oracle_with_artifact_pattern = AST::Oracle {
is_match: true,
conditionals: vec![abyss_core::ast::ConditionalAssignment {
variable: "__match_0".into(),
expression: var("hero"),
line_info: None,
}],
branches: vec![
AST::OracleBranch {
pattern: vec![AST::OracleArtifactPattern {
type_name: "Player".into(),
fields: vec![("name".into(), AST::Var("name".into(), None))],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(var("name"), None)),
line_info: None,
},
AST::OracleBranch {
pattern: vec![AST::OracleArtifactPattern {
type_name: "Player".into(),
fields: vec![
("name".into(), AST::Rune("Ardyn".into(), None)),
("health".into(), AST::Var("health".into(), None)),
],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(var("health"), None)),
line_info: None,
},
],
line_info: None,
};
let oracle_with_artifact_pattern_expected = concat!(
"oracle (hero) {\n",
" Player { name } => reveal name\n",
" Player { name: \"Ardyn\", health } => reveal health\n",
"}"
);
assert_eq!(
format_ast(&oracle_with_artifact_pattern, 0),
oracle_with_artifact_pattern_expected
);
let oracle_with_lexicon_pattern = AST::Oracle {
is_match: true,
conditionals: vec![abyss_core::ast::ConditionalAssignment {
variable: "__match_0".into(),
expression: var("config"),
line_info: None,
}],
branches: vec![
AST::OracleBranch {
pattern: vec![AST::OracleLexiconPattern {
entries: vec![],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(rune("a lexicon"), None)),
line_info: None,
},
AST::OracleBranch {
pattern: vec![AST::OracleLexiconPattern {
entries: vec![
("name".into(), AST::Var("n".into(), None)),
("port".into(), AST::Arcana(8080, None)),
],
line_info: None,
}],
guard: None,
body: Box::new(AST::Reveal(var("n"), None)),
line_info: None,
},
],
line_info: None,
};
let oracle_with_lexicon_pattern_expected = concat!(
"oracle (config) {\n",
" {} => reveal \"a lexicon\"\n",
" { \"name\": n, \"port\": 8080 } => reveal n\n",
"}"
);
assert_eq!(
format_ast(&oracle_with_lexicon_pattern, 0),
oracle_with_lexicon_pattern_expected
);
let orbit = AST::Orbit {
params: vec![AST::OrbitParam {
name: "i".into(),
start: arcana(0),
end: arcana(2),
op: "..".into(),
line_info: None,
}],
body: Box::new(block.clone()),
line_info: None,
};
let orbit_expected = concat!("orbit (i = 0..2)", "{\n reveal sigil;\n}");
assert_eq!(format_ast(&orbit, 0), orbit_expected);
let resume_named = AST::Resume(Some("outer".into()), None);
assert_eq!(format_ast(&resume_named, 0), "resume outer");
let resume_default = AST::Resume(None, None);
assert_eq!(format_ast(&resume_default, 0), "resume");
let eject_named = AST::Eject(Some("inner".into()), None);
assert_eq!(format_ast(&eject_named, 0), "eject inner");
let eject_default = AST::Eject(None, None);
assert_eq!(format_ast(&eject_default, 0), "eject");
let method = AST::Engrave {
name: "ignite".into(),
params: vec![
AST::EngraveParam {
name: "core".into(),
param_type: Type::Artifact("Pyre".into()),
is_morph: true,
line_info: None,
},
AST::EngraveParam {
name: "ember".into(),
param_type: Type::Arcana,
is_morph: false,
line_info: None,
},
],
return_type: Type::Arcana,
body: Box::new(block.clone()),
method_target: Some(ArtifactMethodTarget {
artifact: "Pyre".into(),
requires_morph: true,
}),
line_info: None,
};
let method_expected = concat!(
"engrave Pyre::ignite(morph core, ember: arcana) -> arcana ",
"{\n reveal sigil;\n}"
);
assert_eq!(format_ast(&method, 0), method_expected);
let function = AST::Engrave {
name: "summon".into(),
params: vec![AST::EngraveParam {
name: "target".into(),
param_type: Type::Scroll,
is_morph: false,
line_info: None,
}],
return_type: Type::Abyss,
body: Box::new(block),
method_target: None,
line_info: None,
};
let function_expected = concat!("engrave summon(target: scroll) ", "{\n reveal sigil;\n}");
assert_eq!(format_ast(&function, 0), function_expected);
}
#[test]
fn format_type_keyword_variants() {
let variants = vec![
(Type::Arcana, "arcana".to_string()),
(Type::Aether, "aether".to_string()),
(Type::Rune, "rune".to_string()),
(Type::Omen, "omen".to_string()),
(Type::Abyss, "abyss".to_string()),
(Type::Scroll, "scroll".to_string()),
(Type::Lexicon, "lexicon".to_string()),
(Type::Materia, "materia".to_string()),
(Type::Glyph, "glyph".to_string()),
(Type::Artifact("Relic".into()), "Relic".to_string()),
];
for (index, (ty, expected)) in variants.into_iter().enumerate() {
let param = AST::EngraveParam {
name: format!("param{}", index),
param_type: ty,
is_morph: index % 2 == 1,
line_info: None,
};
let prefix = if index % 2 == 1 { "morph " } else { "" };
let expected_text = format!("{}param{}: {}", prefix, index, expected);
assert_eq!(format_ast(¶m, 0), expected_text);
}
}