iter 22b.4a.3: form-a printer arm for ClassDef

This commit is contained in:
2026-05-09 22:10:35 +02:00
parent da1746229d
commit dd9614cae9
+151 -20
View File
@@ -8,7 +8,8 @@
//! per level. Comments are NOT emitted.
use ailang_core::ast::{
Arm, ConstDef, Ctor, Def, FnDef, Import, Literal, Module, ParamMode, Pattern, Suppress, Term,
Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, InstanceDef,
InstanceMethod, Literal, Module, ParamMode, Pattern, SuperclassRef, Suppress, Term,
Type, TypeDef,
};
@@ -94,25 +95,8 @@ fn write_def(out: &mut String, def: &Def, level: usize) {
Def::Type(td) => write_type_def(out, td, level),
Def::Fn(fd) => write_fn_def(out, fd, level),
Def::Const(cd) => write_const_def(out, cd, level),
// Iter 22b.1: surface (Form-B) printer arms for ClassDef /
// InstanceDef are deferred to 22b.4 (along with the parse-side
// arms in `surface/src/parse.rs`). For 22b.1 we emit a
// single-line placeholder so a module containing these defs
// still round-trips through `ail render` in print-only mode.
Def::Class(c) => {
indent(out, level);
out.push_str("(class ");
out.push_str(&c.name);
out.push(' ');
out.push_str(&c.param);
out.push(')');
}
Def::Instance(i) => {
indent(out, level);
out.push_str("(instance ");
out.push_str(&i.class);
out.push(')');
}
Def::Class(c) => write_class_def(out, c, level),
Def::Instance(i) => write_instance_def(out, i, level),
}
}
@@ -236,6 +220,69 @@ fn write_const_def(out: &mut String, cd: &ConstDef, level: usize) {
out.push(')');
}
fn write_class_def(out: &mut String, c: &ClassDef, level: usize) {
indent(out, level);
out.push_str("(class ");
out.push_str(&c.name);
out.push('\n');
indent(out, level + 1);
out.push_str("(param ");
out.push_str(&c.param);
out.push(')');
if let Some(sc) = &c.superclass {
out.push('\n');
write_superclass(out, sc, level + 1);
}
if let Some(doc) = &c.doc {
out.push('\n');
indent(out, level + 1);
out.push_str("(doc ");
write_string_lit(out, doc);
out.push(')');
}
for m in &c.methods {
out.push('\n');
write_class_method(out, m, level + 1);
}
out.push(')');
}
fn write_superclass(out: &mut String, sc: &SuperclassRef, level: usize) {
indent(out, level);
out.push_str("(superclass (class ");
out.push_str(&sc.class);
out.push_str(") (type ");
out.push_str(&sc.type_);
out.push_str("))");
}
fn write_class_method(out: &mut String, m: &ClassMethod, level: usize) {
indent(out, level);
out.push_str("(method ");
out.push_str(&m.name);
out.push('\n');
indent(out, level + 1);
out.push_str("(type ");
write_type(out, &m.ty);
out.push(')');
if let Some(default) = &m.default {
out.push('\n');
indent(out, level + 1);
out.push_str("(default ");
write_term(out, default, level + 2);
out.push(')');
}
out.push(')');
}
fn write_instance_def(out: &mut String, _i: &InstanceDef, _level: usize) {
// Task 4: real implementation. This stub keeps the workspace
// compiling; it intentionally produces an empty form so that
// any test that exercises Instance round-trip will fail
// (which it should — Task 4 is RED for instance-side).
out.push_str("(instance)");
}
// ---- types ----------------------------------------------------------------
/// Iter 18a: print one fn-type param/ret slot, wrapping with
@@ -501,3 +548,87 @@ fn write_pattern(out: &mut String, p: &Pattern) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use ailang_core::ast::{
ClassDef, ClassMethod, Def, InstanceDef, InstanceMethod, Literal, Module,
ParamMode, SuperclassRef, Term, Type,
};
fn round_trip(m: Module, label: &str) {
let printed = print(&m);
let parsed = crate::parse::parse(&printed)
.unwrap_or_else(|e| panic!(
"{label}: re-parse failed: {e:?}\nprinted:\n{printed}"
));
let bytes_orig = ailang_core::canonical::to_bytes(&m);
let bytes_back = ailang_core::canonical::to_bytes(&parsed);
if bytes_orig != bytes_back {
let s_orig = String::from_utf8_lossy(&bytes_orig).into_owned();
let s_back = String::from_utf8_lossy(&bytes_back).into_owned();
panic!(
"{label}: canonical bytes differ.\noriginal: {s_orig}\nround: {s_back}\nprinted:\n{printed}"
);
}
}
#[test]
fn print_then_parse_round_trip_minimal_class() {
let m = Module {
schema: ailang_core::SCHEMA.to_string(),
name: "M".into(),
imports: vec![],
defs: vec![Def::Class(ClassDef {
name: "Foo".into(),
param: "a".into(),
superclass: None,
methods: vec![ClassMethod {
name: "m".into(),
ty: Type::Fn {
params: vec![Type::Var { name: "a".into() }],
param_modes: vec![ParamMode::Implicit],
ret: Box::new(Type::int()),
ret_mode: ParamMode::Implicit,
effects: vec![],
},
default: None,
}],
doc: None,
})],
};
round_trip(m, "minimal_class");
}
#[test]
fn print_then_parse_round_trip_class_with_superclass_and_default() {
let m = Module {
schema: ailang_core::SCHEMA.to_string(),
name: "M".into(),
imports: vec![],
defs: vec![Def::Class(ClassDef {
name: "Bar".into(),
param: "a".into(),
superclass: Some(SuperclassRef {
class: "Foo".into(),
type_: "a".into(),
}),
methods: vec![ClassMethod {
name: "m".into(),
ty: Type::Fn {
params: vec![Type::Var { name: "a".into() }],
param_modes: vec![ParamMode::Implicit],
ret: Box::new(Type::int()),
ret_mode: ParamMode::Implicit,
effects: vec![],
},
default: Some(Term::Lit { lit: Literal::Int { value: 0 } }),
}],
doc: Some("docline".into()),
})],
};
round_trip(m, "class_with_superclass_and_default");
}
}