iter 22b.4a.3: form-a printer arm for ClassDef
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user