From 6561c4c0e643e85a81b2902452036a8613a398c0 Mon Sep 17 00:00:00 2001 From: mizchi Date: Fri, 16 Jan 2026 00:15:06 +0900 Subject: [PATCH 1/3] feat: add printer package for AST to source code conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new printer package that converts MoonBit AST back to source code. Features: - Support all top-level declarations (functions, structs, enums, traits, impl, type aliases, etc.) - Support all expression types with proper operator precedence - Support patterns, types, and constants - Tab-based indentation for nested structures - Round-trip test suite to verify parse → print → parse consistency Files: - printer/printer.mbt: Top-level declarations and main entry points - printer/expr.mbt: Expression printing with indentation support - printer/pattern.mbt: Pattern printing - printer/type.mbt: Type printing - printer/constant.mbt: Constant literal printing - printer/printer_test.mbt: Unit tests and round-trip tests Co-Authored-By: Claude Opus 4.5 --- printer/constant.mbt | 57 +++ printer/expr.mbt | 985 +++++++++++++++++++++++++++++++++++++++ printer/moon.pkg | 6 + printer/pattern.mbt | 251 ++++++++++ printer/printer.mbt | 777 ++++++++++++++++++++++++++++++ printer/printer_test.mbt | 285 +++++++++++ printer/type.mbt | 92 ++++ 7 files changed, 2453 insertions(+) create mode 100644 printer/constant.mbt create mode 100644 printer/expr.mbt create mode 100644 printer/moon.pkg create mode 100644 printer/pattern.mbt create mode 100644 printer/printer.mbt create mode 100644 printer/printer_test.mbt create mode 100644 printer/type.mbt diff --git a/printer/constant.mbt b/printer/constant.mbt new file mode 100644 index 0000000..36d87bf --- /dev/null +++ b/printer/constant.mbt @@ -0,0 +1,57 @@ +///| +/// Print a constant value. +pub fn print_constant(buf : StringBuilder, c : @syntax.Constant) -> Unit { + match c { + Bool(b) => buf.write_string(if b { "true" } else { "false" }) + Byte(repr) => { + buf.write_string("b'") + buf.write_string(repr) + buf.write_char('\'') + } + Bytes(repr) => { + buf.write_string("b\"") + buf.write_string(repr) + buf.write_char('"') + } + Char(repr) => { + buf.write_char('\'') + buf.write_string(repr) + buf.write_char('\'') + } + Int(s) => buf.write_string(s) + Int64(s) => { + buf.write_string(s) + buf.write_char('L') + } + UInt(s) => { + buf.write_string(s) + buf.write_char('U') + } + UInt64(s) => { + buf.write_string(s) + buf.write_string("UL") + } + Float(s) => { + buf.write_string(s) + if not(s.contains(".")) { + buf.write_string(".0") + } + buf.write_char('F') + } + Double(s) => { + buf.write_string(s) + if not(s.contains(".")) { + buf.write_string(".0") + } + } + String(repr) => { + buf.write_char('"') + buf.write_string(repr) + buf.write_char('"') + } + BigInt(s) => { + buf.write_string(s) + buf.write_char('N') + } + } +} diff --git a/printer/expr.mbt b/printer/expr.mbt new file mode 100644 index 0000000..bb1ae11 --- /dev/null +++ b/printer/expr.mbt @@ -0,0 +1,985 @@ +///| +/// Write indentation (tabs) +fn print_indent(buf : StringBuilder, indent : Int) -> Unit { + for _ in 0.. String { + match id { + Ident(name~) => name + Dot(pkg~, id~) => "@" + pkg + "." + id + } +} + +///| +/// Get operator precedence (higher = binds tighter) +fn get_precedence(op : String) -> Int { + // MoonBit operator precedence (based on common conventions) + match op { + "||" => 1 + "&&" => 2 + "==" | "!=" | "<" | ">" | "<=" | ">=" => 3 + "|" => 4 + "^" => 5 + "&" => 6 + "<<" | ">>" => 7 + "+" | "-" => 8 + "*" | "/" | "%" => 9 + _ => 10 // custom operators, highest precedence + } +} + +///| +/// Check if expression needs parentheses in a binary context +fn needs_parens(expr : @syntax.Expr, parent_prec : Int) -> Bool { + match expr { + Infix(op~, ..) => get_precedence(long_ident_to_string(op.name)) < parent_prec + _ => false + } +} + +///| +/// Print expression with optional parentheses based on precedence +fn print_expr_with_prec( + buf : StringBuilder, + expr : @syntax.Expr, + parent_prec : Int, + indent : Int +) -> Unit { + if needs_parens(expr, parent_prec) { + buf.write_char('(') + print_expr(buf, expr, indent~) + buf.write_char(')') + } else { + print_expr(buf, expr, indent~) + } +} + +///| +/// Print an expression. +pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) -> Unit { + match expr { + // Literals + Constant(c~, ..) => print_constant(buf, c) + Unit(..) => buf.write_string("()") + + // Identifiers + Ident(id~, ..) => print_var(buf, id) + Constr(constr~, ..) => print_constructor(buf, constr) + + // Collections + Array(exprs~, ..) => { + buf.write_char('[') + let mut first = true + for e in exprs { + if not(first) { + buf.write_string(", ") + } + first = false + print_expr(buf, e) + } + buf.write_char(']') + } + ArraySpread(elems~, ..) => { + buf.write_char('[') + let mut first = true + for e in elems { + if not(first) { + buf.write_string(", ") + } + first = false + match e { + Regular(expr) => print_expr(buf, expr) + Spread(expr~, ..) => { + buf.write_string("..") + print_expr(buf, expr) + } + } + } + buf.write_char(']') + } + Tuple(exprs~, ..) => { + buf.write_char('(') + let mut first = true + for e in exprs { + if not(first) { + buf.write_string(", ") + } + first = false + print_expr(buf, e) + } + buf.write_char(')') + } + + // Operators + Apply(func~, args~, attr~, ..) => { + print_expr(buf, func) + buf.write_char('(') + print_arguments(buf, args) + buf.write_char(')') + match attr { + NoAttr => () + Exclamation => buf.write_char('!') + Question => buf.write_char('?') + } + } + Infix(op~, lhs~, rhs~, ..) => { + let prec = get_precedence(long_ident_to_string(op.name)) + print_expr_with_prec(buf, lhs, prec, indent) + buf.write_char(' ') + print_var(buf, op) + buf.write_char(' ') + print_expr_with_prec(buf, rhs, prec + 1, indent) // right associative needs +1 + } + Unary(op~, expr~, ..) => { + print_var(buf, op) + print_expr(buf, expr) + } + Pipe(lhs~, rhs~, ..) => { + print_expr(buf, lhs) + buf.write_string(" |> ") + print_expr(buf, rhs) + } + + // Array operations + ArrayGet(array~, index~, ..) => { + print_expr(buf, array) + buf.write_char('[') + print_expr(buf, index) + buf.write_char(']') + } + ArrayGetSlice(array~, start_index~, end_index~, ..) => { + print_expr(buf, array) + buf.write_char('[') + match start_index { + Some(e) => print_expr(buf, e) + None => () + } + buf.write_char(':') + match end_index { + Some(e) => print_expr(buf, e) + None => () + } + buf.write_char(']') + } + ArraySet(array~, index~, value~, ..) => { + print_expr(buf, array) + buf.write_char('[') + print_expr(buf, index) + buf.write_string("] = ") + print_expr(buf, value) + } + ArrayAugmentedSet(op~, array~, index~, value~, ..) => { + print_expr(buf, array) + buf.write_char('[') + print_expr(buf, index) + buf.write_string("] ") + print_var(buf, op) + buf.write_string("= ") + print_expr(buf, value) + } + + // Control flow + If(cond~, ifso~, ifnot~, ..) => { + buf.write_string("if ") + print_expr(buf, cond) + buf.write_string(" {\n") + print_expr(buf, ifso) + buf.write_string("\n}") + match ifnot { + Some(e) => { + buf.write_string(" else {\n") + print_expr(buf, e) + buf.write_string("\n}") + } + None => () + } + } + Match(expr~, cases~, ..) => { + buf.write_string("match ") + print_expr(buf, expr, indent~) + buf.write_string(" {\n") + for c in cases { + print_indent(buf, indent + 1) + print_pattern(buf, c.pattern) + match c.guard_ { + Some(g) => { + buf.write_string(" if ") + print_expr(buf, g, indent~) + } + None => () + } + buf.write_string(" => ") + print_expr(buf, c.body, indent=indent + 1) + buf.write_char('\n') + } + print_indent(buf, indent) + buf.write_char('}') + } + While(loop_cond~, loop_body~, while_else~, label~, ..) => { + match label { + Some(l) => { + buf.write_string(l.name) + buf.write_string("~ ") + } + None => () + } + buf.write_string("while ") + print_expr(buf, loop_cond, indent~) + buf.write_string(" {\n") + print_indent(buf, indent + 1) + print_expr(buf, loop_body, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + match while_else { + Some(e) => { + buf.write_string(" else {\n") + print_indent(buf, indent + 1) + print_expr(buf, e, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + } + None => () + } + } + For(binders~, condition~, continue_block~, body~, for_else~, label~, ..) => { + match label { + Some(l) => { + buf.write_string(l.name) + buf.write_string("~ ") + } + None => () + } + buf.write_string("for ") + let mut first = true + for b in binders { + if not(first) { + buf.write_string(", ") + } + first = false + buf.write_string(b.0.name) + buf.write_string(" = ") + print_expr(buf, b.1, indent~) + } + match condition { + Some(c) => { + buf.write_string("; ") + print_expr(buf, c, indent~) + } + None => () + } + if not(continue_block.is_empty()) { + buf.write_string("; ") + let mut first_c = true + for b in continue_block { + if not(first_c) { + buf.write_string(", ") + } + first_c = false + buf.write_string(b.0.name) + buf.write_string(" = ") + print_expr(buf, b.1, indent~) + } + } + buf.write_string(" {\n") + print_indent(buf, indent + 1) + print_expr(buf, body, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + match for_else { + Some(e) => { + buf.write_string(" else {\n") + print_indent(buf, indent + 1) + print_expr(buf, e, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + } + None => () + } + } + ForEach(binders~, expr~, body~, else_block~, label~, ..) => { + match label { + Some(l) => { + buf.write_string(l.name) + buf.write_string("~ ") + } + None => () + } + buf.write_string("for ") + let mut first = true + for b in binders { + if not(first) { + buf.write_string(", ") + } + first = false + match b { + Some(binder) => buf.write_string(binder.name) + None => buf.write_char('_') + } + } + buf.write_string(" in ") + print_expr(buf, expr, indent~) + buf.write_string(" {\n") + print_indent(buf, indent + 1) + print_expr(buf, body, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + match else_block { + Some(e) => { + buf.write_string(" else {\n") + print_indent(buf, indent + 1) + print_expr(buf, e, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + } + None => () + } + } + Loop(arg~, body~, label~, ..) => { + match label { + Some(l) => { + buf.write_string(l.name) + buf.write_string("~ ") + } + None => () + } + buf.write_string("loop ") + print_expr(buf, arg, indent~) + buf.write_string(" {\n") + for c in body { + print_indent(buf, indent + 1) + print_pattern(buf, c.pattern) + match c.guard_ { + Some(g) => { + buf.write_string(" if ") + print_expr(buf, g, indent~) + } + None => () + } + buf.write_string(" => ") + print_expr(buf, c.body, indent=indent + 1) + buf.write_char('\n') + } + print_indent(buf, indent) + buf.write_char('}') + } + + // Functions + Function(func~, ..) => { + match func.kind { + Lambda => { + buf.write_string("fn(") + print_parameters(buf, func.parameters) + buf.write_char(')') + match func.return_type { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + buf.write_string(" {\n") + print_indent(buf, indent + 1) + print_expr(buf, func.body, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + } + Arrow => { + buf.write_char('(') + print_parameters(buf, func.parameters) + buf.write_string(") => ") + print_expr(buf, func.body, indent~) + } + } + } + + // Let bindings + Let(pattern~, expr~, body~, ..) => { + buf.write_string("let ") + print_pattern(buf, pattern) + buf.write_string(" = ") + print_expr(buf, expr, indent~) + buf.write_char('\n') + print_indent(buf, indent) + print_expr(buf, body, indent~) + } + LetMut(binder~, ty~, expr~, body~, ..) => { + buf.write_string("let mut ") + buf.write_string(binder.name) + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + buf.write_string(" = ") + print_expr(buf, expr) + buf.write_char('\n') + print_expr(buf, body) + } + LetFn(name~, func~, body~, ..) => { + buf.write_string("fn ") + buf.write_string(name.name) + buf.write_char('(') + print_parameters(buf, func.parameters) + buf.write_char(')') + match func.return_type { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + buf.write_string(" {\n") + print_expr(buf, func.body) + buf.write_string("\n}\n") + print_expr(buf, body) + } + LetRec(bindings~, body~, ..) => { + for b in bindings { + buf.write_string("fn ") + buf.write_string(b.0.name) + buf.write_char('(') + print_parameters(buf, b.1.parameters) + buf.write_char(')') + match b.1.return_type { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + buf.write_string(" {\n") + print_expr(buf, b.1.body) + buf.write_string("\n}\n") + } + print_expr(buf, body) + } + LetAnd(bindings~, body~, ..) => { + let mut first = true + for b in bindings { + if first { + buf.write_string("let ") + first = false + } else { + buf.write_string("and ") + } + buf.write_string(b.0.name) + match b.1 { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + buf.write_string(" = fn(") + print_parameters(buf, b.2.parameters) + buf.write_string(") {\n") + print_expr(buf, b.2.body) + buf.write_string("\n}\n") + } + print_expr(buf, body) + } + + // Sequences + Sequence(exprs~, last_expr~, ..) => { + for e in exprs { + print_expr(buf, e) + buf.write_char('\n') + } + print_expr(buf, last_expr) + } + + // Records + Record(type_name~, fields~, ..) => { + match type_name { + Some(tn) => { + print_type_name(buf, tn) + buf.write_string("::") + } + None => () + } + buf.write_string("{ ") + let mut first = true + for f in fields { + if not(first) { + buf.write_string(", ") + } + first = false + if f.is_pun { + buf.write_string(f.label.name) + } else { + buf.write_string(f.label.name) + buf.write_string(": ") + print_expr(buf, f.expr) + } + } + buf.write_string(" }") + } + RecordUpdate(type_name~, record~, fields~, ..) => { + match type_name { + Some(tn) => { + print_type_name(buf, tn) + buf.write_string("::") + } + None => () + } + buf.write_string("{ ..") + print_expr(buf, record) + for f in fields { + buf.write_string(", ") + if f.is_pun { + buf.write_string(f.label.name) + } else { + buf.write_string(f.label.name) + buf.write_string(": ") + print_expr(buf, f.expr) + } + } + buf.write_string(" }") + } + Field(record~, accessor~, ..) => { + print_expr(buf, record) + buf.write_char('.') + match accessor { + Label(label) => buf.write_string(label.name) + Index(tuple_index~, ..) => buf.write_string(tuple_index.to_string()) + Newtype(..) => buf.write_char('_') + } + } + Method(type_name~, method_name~, ..) => { + print_type_name(buf, type_name) + buf.write_string("::") + buf.write_string(method_name.name) + } + DotApply(self~, method_name~, args~, return_self~, attr~, ..) => { + print_expr(buf, self) + buf.write_char('.') + buf.write_string(method_name.name) + buf.write_char('(') + print_arguments(buf, args) + buf.write_char(')') + match attr { + NoAttr => () + Exclamation => buf.write_char('!') + Question => buf.write_char('?') + } + if return_self { + buf.write_char('.') + } + } + + // Mutation and assignment + Mutate(record~, accessor~, field~, augmented_by~, ..) => { + print_expr(buf, record) + buf.write_char('.') + match accessor { + Label(label) => buf.write_string(label.name) + Index(tuple_index~, ..) => buf.write_string(tuple_index.to_string()) + Newtype(..) => buf.write_char('_') + } + match augmented_by { + Some(op) => { + buf.write_char(' ') + print_var(buf, op) + buf.write_string("= ") + } + None => buf.write_string(" = ") + } + print_expr(buf, field) + } + Assign(var_~, expr~, augmented_by~, ..) => { + print_var(buf, var_) + match augmented_by { + Some(op) => { + buf.write_char(' ') + print_var(buf, op) + buf.write_string("= ") + } + None => buf.write_string(" = ") + } + print_expr(buf, expr) + } + + // Type constraints + Constraint(expr~, ty~, ..) => { + buf.write_char('(') + print_expr(buf, expr) + buf.write_string(" : ") + print_type(buf, ty) + buf.write_char(')') + } + As(expr~, trait_~, ..) => { + print_expr(buf, expr) + buf.write_string(" as ") + print_type_name(buf, trait_) + } + Is(expr~, pat~, ..) => { + print_expr(buf, expr) + buf.write_string(" is ") + print_pattern(buf, pat) + } + + // Control flow keywords + Return(return_value~, ..) => { + buf.write_string("return") + match return_value { + Some(e) => { + buf.write_char(' ') + print_expr(buf, e) + } + None => () + } + } + Raise(err_value~, ..) => { + buf.write_string("raise ") + print_expr(buf, err_value) + } + Break(arg~, label~, ..) => { + buf.write_string("break") + match label { + Some(l) => { + buf.write_char(' ') + buf.write_string(l.name) + } + None => () + } + match arg { + Some(e) => { + buf.write_char(' ') + print_expr(buf, e) + } + None => () + } + } + Continue(args~, label~, ..) => { + buf.write_string("continue") + match label { + Some(l) => { + buf.write_char(' ') + buf.write_string(l.name) + } + None => () + } + if not(args.is_empty()) { + buf.write_char(' ') + let mut first = true + for e in args { + if not(first) { + buf.write_string(", ") + } + first = false + print_expr(buf, e) + } + } + } + + // Error handling + Try(body~, catch_~, try_else~, has_try~, ..) => { + if has_try { + buf.write_string("try {\n") + print_expr(buf, body) + buf.write_string("\n}") + } else { + print_expr(buf, body) + } + if not(catch_.is_empty()) { + buf.write_string(" catch {\n") + for c in catch_ { + buf.write_string(" ") + print_pattern(buf, c.pattern) + buf.write_string(" => ") + print_expr(buf, c.body) + buf.write_char('\n') + } + buf.write_char('}') + } + match try_else { + Some(cases) => { + buf.write_string(" else {\n") + for c in cases { + buf.write_string(" ") + print_pattern(buf, c.pattern) + buf.write_string(" => ") + print_expr(buf, c.body) + buf.write_char('\n') + } + buf.write_char('}') + } + None => () + } + } + TryOperator(body~, kind~, ..) => { + match kind { + Question => buf.write_string("try? ") + Exclamation => buf.write_string("try! ") + } + print_expr(buf, body) + } + + // Guard + Guard(cond~, otherwise~, body~, ..) => { + buf.write_string("guard ") + print_expr(buf, cond) + match otherwise { + Some(e) => { + buf.write_string(" else {\n") + print_expr(buf, e) + buf.write_string("\n}") + } + None => () + } + buf.write_char('\n') + print_expr(buf, body) + } + + // Defer + Defer(expr~, body~, ..) => { + buf.write_string("defer ") + print_expr(buf, expr) + buf.write_char('\n') + print_expr(buf, body) + } + + // Hole + Hole(kind~, ..) => { + match kind { + Synthesized => buf.write_char('_') + Incomplete => buf.write_string("...") + Todo => buf.write_string("todo") + } + } + + // Map + Map(elems~, ..) => { + buf.write_string("{ ") + let mut first = true + for e in elems { + if not(first) { + buf.write_string(", ") + } + first = false + print_constant(buf, e.key) + buf.write_string(": ") + print_expr(buf, e.expr) + } + buf.write_string(" }") + } + + // Group + Group(expr~, group~, ..) => { + match group { + Paren => { + buf.write_char('(') + print_expr(buf, expr) + buf.write_char(')') + } + Brace => { + buf.write_string("{\n") + print_expr(buf, expr) + buf.write_string("\n}") + } + } + } + + // String interpolation + Interp(elems~, ..) => { + buf.write_char('"') + for e in elems { + match e { + Literal(repr~, ..) => buf.write_string(repr) + Expr(expr~, ..) => { + buf.write_string("\\{") + print_expr(buf, expr) + buf.write_char('}') + } + Source(_) => () + } + } + buf.write_char('"') + } + MultilineString(elems~, ..) => { + for e in elems { + match e { + String(s) => { + buf.write_string("#| ") + buf.write_string(s) + buf.write_char('\n') + } + Interp(interp_elems) => { + buf.write_string("#| ") + for ie in interp_elems { + match ie { + Literal(repr~, ..) => buf.write_string(repr) + Expr(expr~, ..) => { + buf.write_string("\\{") + print_expr(buf, expr) + buf.write_char('}') + } + Source(_) => () + } + } + buf.write_char('\n') + } + } + } + } + + // LexMatch + LexMatch(strategy~, expr~, cases~, ..) => { + buf.write_string("lexmatch ") + match strategy { + Some(s) => { + buf.write_string("using ") + buf.write_string(s.name) + buf.write_char(' ') + } + None => () + } + print_expr(buf, expr) + buf.write_string(" {\n") + for c in cases { + buf.write_string(" ") + print_lex_top_patterns(buf, c.pat) + buf.write_string(" => ") + print_expr(buf, c.body) + buf.write_char('\n') + } + buf.write_char('}') + } + IsLexMatch(expr~, strategy~, pat~, ..) => { + print_expr(buf, expr) + buf.write_string(" lexmatch") + match strategy { + Some(s) => { + buf.write_char('?') + buf.write_char(' ') + buf.write_string(s.name) + } + None => buf.write_char('?') + } + buf.write_char(' ') + print_lex_top_patterns(buf, pat) + } + // StaticAssert is compiler-generated, just print the body + StaticAssert(body~, ..) => print_expr(buf, body) + } +} + +///| +fn print_var(buf : StringBuilder, v : @syntax.Var) -> Unit { + print_long_ident(buf, v.name) +} + +///| +fn print_arguments( + buf : StringBuilder, + args : @list.List[@syntax.Argument] +) -> Unit { + let mut first = true + for arg in args { + if not(first) { + buf.write_string(", ") + } + first = false + match arg.kind { + Positional => () + Labelled(label) => { + buf.write_string(label.name) + buf.write_string("=") + } + LabelledPun(label) => { + buf.write_string(label.name) + buf.write_char('~') + continue // skip value printing for pun + } + LabelledOption(label~, ..) => { + buf.write_string(label.name) + buf.write_string("?=") + } + LabelledOptionPun(label~, ..) => { + buf.write_string(label.name) + buf.write_char('?') + continue // skip value printing for pun + } + } + print_expr(buf, arg.value) + } +} + +///| +fn print_lex_top_patterns( + buf : StringBuilder, + pats : @list.List[@syntax.LexTopPattern] +) -> Unit { + let mut first = true + for p in pats { + if not(first) { + buf.write_char(' ') + } + first = false + print_lex_top_pattern(buf, p) + } +} + +///| +fn print_lex_top_pattern(buf : StringBuilder, pat : @syntax.LexTopPattern) -> Unit { + match pat { + Pattern(lex_pat) => print_lex_pattern(buf, lex_pat) + Binder(b) => buf.write_string(b.name) + Wildcard(..) => buf.write_char('_') + } +} + +///| +fn print_lex_pattern(buf : StringBuilder, pat : @syntax.LexPattern) -> Unit { + match pat { + Regex(lit~, ..) => { + buf.write_char('/') + buf.write_string(lit) + buf.write_char('/') + } + RegexInterp(elems~, ..) => { + buf.write_char('/') + for e in elems { + match e { + Literal(repr~, ..) => buf.write_string(repr) + Expr(expr~, ..) => { + buf.write_string("\\{") + print_expr(buf, expr) + buf.write_char('}') + } + Source(_) => () + } + } + buf.write_char('/') + } + Alias(pat~, binder~, ..) => { + print_lex_pattern(buf, pat) + buf.write_string(" as ") + buf.write_string(binder.name) + } + Sequence(pats~, ..) => { + let mut first = true + for p in pats { + if not(first) { + buf.write_char(' ') + } + first = false + print_lex_pattern(buf, p) + } + } + } +} diff --git a/printer/moon.pkg b/printer/moon.pkg new file mode 100644 index 0000000..45d6484 --- /dev/null +++ b/printer/moon.pkg @@ -0,0 +1,6 @@ +import { + "moonbitlang/parser/syntax", + "moonbitlang/parser/tokens", + "moonbitlang/parser", + "moonbitlang/parser/attribute", +} diff --git a/printer/pattern.mbt b/printer/pattern.mbt new file mode 100644 index 0000000..3b784b8 --- /dev/null +++ b/printer/pattern.mbt @@ -0,0 +1,251 @@ +///| +/// Print a pattern. +pub fn print_pattern(buf : StringBuilder, pat : @syntax.Pattern) -> Unit { + match pat { + Alias(pat~, alias_~, ..) => { + print_pattern(buf, pat) + buf.write_string(" as ") + buf.write_string(alias_.name) + } + Any(..) => buf.write_char('_') + Array(pats~, ..) => { + buf.write_char('[') + print_array_patterns(buf, pats) + buf.write_char(']') + } + Constant(c~, ..) => print_constant(buf, c) + Constraint(pat~, ty~, ..) => { + buf.write_char('(') + print_pattern(buf, pat) + buf.write_string(" : ") + print_type(buf, ty) + buf.write_char(')') + } + Constr(constr~, args~, is_open~, ..) => { + print_constructor(buf, constr) + match args { + Some(args) if not(args.is_empty()) => { + buf.write_char('(') + print_constr_pat_args(buf, args) + if is_open { + buf.write_string(", ..") + } + buf.write_char(')') + } + _ => + if is_open { + buf.write_string("(..)") + } + } + } + Or(pat1~, pat2~, ..) => { + print_pattern(buf, pat1) + buf.write_string(" | ") + print_pattern(buf, pat2) + } + Tuple(pats~, ..) => { + buf.write_char('(') + let mut first = true + for p in pats { + if not(first) { + buf.write_string(", ") + } + first = false + print_pattern(buf, p) + } + buf.write_char(')') + } + Var(binder) => buf.write_string(binder.name) + Record(fields~, is_closed~, ..) => { + buf.write_string("{ ") + let mut first = true + for f in fields { + if not(first) { + buf.write_string(", ") + } + first = false + if f.is_pun { + buf.write_string(f.label.name) + } else { + buf.write_string(f.label.name) + buf.write_string(": ") + print_pattern(buf, f.pattern) + } + } + if not(is_closed) { + if not(fields.is_empty()) { + buf.write_string(", ") + } + buf.write_string("..") + } + buf.write_string(" }") + } + Map(elems~, is_closed~, ..) => { + buf.write_string("{ ") + let mut first = true + for e in elems { + if not(first) { + buf.write_string(", ") + } + first = false + print_constant(buf, e.key) + buf.write_string(": ") + if e.match_absent { + buf.write_char('?') + } + print_pattern(buf, e.pat) + } + if not(is_closed) { + if not(elems.is_empty()) { + buf.write_string(", ") + } + buf.write_string("..") + } + buf.write_string(" }") + } + Range(lhs~, rhs~, kind~, ..) => { + print_pattern(buf, lhs) + match kind { + Inclusive | InclusiveMissingEqual => buf.write_string("..=") + Exclusive => buf.write_string("..<") + } + print_pattern(buf, rhs) + } + SpecialConstr(binder~, args~, ..) => { + buf.write_string(binder.name) + buf.write_char('(') + print_constr_pat_args(buf, args) + buf.write_char(')') + } + } +} + +///| +fn print_array_patterns( + buf : StringBuilder, + pats : @syntax.ArrayPatterns +) -> Unit { + match pats { + Closed(pats) => { + let mut first = true + for p in pats { + if not(first) { + buf.write_string(", ") + } + first = false + print_array_pattern(buf, p) + } + } + Open(before, after, binder) => { + let mut first = true + for p in before { + if not(first) { + buf.write_string(", ") + } + first = false + print_array_pattern(buf, p) + } + if not(first) { + buf.write_string(", ") + } + match binder { + Underscore => buf.write_string(".._") + NoBinder => buf.write_string("..") + BinderAs(b) => { + buf.write_string(".. as ") + buf.write_string(b.name) + } + Binder(b) => { + buf.write_string("..") + buf.write_string(b.name) + } + } + for p in after { + buf.write_string(", ") + print_array_pattern(buf, p) + } + } + } +} + +///| +fn print_array_pattern(buf : StringBuilder, p : @syntax.ArrayPattern) -> Unit { + match p { + Pattern(pat) => print_pattern(buf, pat) + StringSpread(str~, ..) => { + buf.write_string("..\"") + buf.write_string(str) + buf.write_char('"') + } + BytesSpread(bytes~, ..) => { + buf.write_string("..b\"") + buf.write_string(bytes) + buf.write_char('"') + } + ConstSpread(binder~, pkg~, ..) => { + buf.write_string("..") + match pkg { + Some(p) => { + buf.write_char('@') + buf.write_string(p) + buf.write_char('.') + } + None => () + } + buf.write_string(binder.name) + } + } +} + +///| +fn print_constr_pat_args( + buf : StringBuilder, + args : @list.List[@syntax.ConstrPatArg] +) -> Unit { + let mut first = true + for arg in args { + if not(first) { + buf.write_string(", ") + } + first = false + match arg.kind { + Positional => () + Labelled(label) => { + buf.write_string(label.name) + buf.write_string("=") + } + LabelledPun(label) => { + buf.write_string(label.name) + buf.write_char('~') + continue // skip pattern printing for pun + } + LabelledOption(label~, ..) => { + buf.write_string(label.name) + buf.write_string("?=") + } + LabelledOptionPun(label~, ..) => { + buf.write_string(label.name) + buf.write_char('?') + continue // skip pattern printing for pun + } + } + print_pattern(buf, arg.pat) + } +} + +///| +fn print_constructor(buf : StringBuilder, c : @syntax.Constructor) -> Unit { + match c.extra_info { + TypeName(tn) => { + print_type_name(buf, tn) + buf.write_string("::") + } + Package(pkg) => { + buf.write_char('@') + buf.write_string(pkg) + buf.write_string("::") + } + NoExtraInfo => () + } + buf.write_string(c.name.name) +} diff --git a/printer/printer.mbt b/printer/printer.mbt new file mode 100644 index 0000000..20868b2 --- /dev/null +++ b/printer/printer.mbt @@ -0,0 +1,777 @@ +///| +/// A simple pretty printer for MoonBit AST. +/// This module provides functions to convert AST nodes back to MoonBit source code. + +///| +/// Print a list of top-level implementations to a string. +pub fn print_impls(impls : @syntax.Impls) -> String { + let buf = StringBuilder::new() + for impl_ in impls { + print_impl(buf, impl_) + buf.write_char('\n') + } + buf.to_string() +} + +///| +/// Print a single top-level implementation. +fn print_impl(buf : StringBuilder, impl_ : @syntax.Impl) -> Unit { + match impl_ { + TopFuncDef(fun_decl~, decl_body~, ..) => { + print_doc_string(buf, fun_decl.doc) + print_attributes(buf, fun_decl.attrs) + print_fun_decl(buf, fun_decl) + print_decl_body(buf, decl_body) + } + TopTypeDef(type_decl) => { + print_doc_string(buf, type_decl.doc) + print_attributes(buf, type_decl.attrs) + print_type_decl(buf, type_decl) + } + TopLetDef(binder~, ty~, expr~, vis~, is_constant~, attrs~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + if is_constant { + buf.write_string("const ") + } else { + buf.write_string("let ") + } + buf.write_string(binder.name) + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + buf.write_string(" = ") + print_expr(buf, expr) + } + TopTest(expr~, name~, attrs~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + buf.write_string("test ") + match name { + Some((lit, _)) => { + buf.write_char('"') + buf.write_string(lit) + buf.write_char('"') + buf.write_char(' ') + } + None => () + } + buf.write_string("{\n") + print_expr(buf, expr) + buf.write_string("\n}") + } + TopTrait(trait_decl) => { + print_doc_string(buf, trait_decl.doc) + print_attributes(buf, trait_decl.attrs) + print_trait_decl(buf, trait_decl) + } + TopImpl( + self_ty~, + trait_~, + method_name~, + quantifiers~, + params~, + ret_ty~, + body~, + attrs~, + doc~, + ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + buf.write_string("impl ") + print_quantifiers(buf, quantifiers) + print_type_name(buf, trait_) + match self_ty { + Some(t) => { + buf.write_string(" for ") + print_type(buf, t) + } + None => () + } + buf.write_string(" with ") + buf.write_string(method_name.name) + buf.write_char('(') + print_parameters(buf, params) + buf.write_char(')') + match ret_ty { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + buf.write_char(' ') + print_decl_body(buf, body) + } + TopExpr(expr~, ..) => print_expr(buf, expr) + TopFuncAlias(pkg~, type_name~, targets~, vis~, attrs~, is_list~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("fnalias ") + match pkg { + Some(p) => { + buf.write_char('@') + buf.write_string(p.name) + } + None => () + } + match type_name { + Some(tn) => { + if pkg is Some(_) { + buf.write_char('.') + } + buf.write_string(tn.name) + buf.write_string("::") + } + None => + if pkg is Some(_) { + buf.write_char('.') + } + } + print_alias_targets(buf, targets, is_list) + } + TopBatchTypeAlias(pkg~, targets~, vis~, attrs~, is_list~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("typealias ") + match pkg { + Some(p) => { + buf.write_char('@') + buf.write_string(p.name) + buf.write_char('.') + } + None => () + } + print_alias_targets(buf, targets, is_list) + } + TopBatchTraitAlias(pkg~, targets~, vis~, attrs~, is_list~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("traitalias ") + match pkg { + Some(p) => { + buf.write_char('@') + buf.write_string(p.name) + buf.write_char('.') + } + None => () + } + print_alias_targets(buf, targets, is_list) + } + TopView( + quantifiers~, + source_ty~, + view_type_name~, + view_constrs~, + view_func_name~, + parameters~, + body~, + vis~, + attrs~, + doc~, + ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("enumview ") + print_quantifiers(buf, quantifiers) + buf.write_string(view_type_name) + buf.write_string(" {\n") + for constr in view_constrs { + buf.write_string(" ") + buf.write_string(constr.name.name) + match constr.args { + Some(args) if not(args.is_empty()) => { + buf.write_char('(') + print_constr_params(buf, args) + buf.write_char(')') + } + _ => () + } + buf.write_char('\n') + } + buf.write_string("} for ") + print_type(buf, source_ty) + buf.write_string(" with ") + buf.write_string(view_func_name.name) + buf.write_char('(') + print_parameters(buf, parameters) + buf.write_string(") {\n") + print_expr(buf, body) + buf.write_string("\n}") + } + TopImplRelation(self_ty~, trait_~, quantifiers~, vis~, attrs~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("impl ") + print_quantifiers(buf, quantifiers) + print_type_name(buf, trait_) + buf.write_string(" for ") + print_type(buf, self_ty) + } + TopUsing(pkg~, names~, vis~, attrs~, doc~, ..) => { + print_doc_string(buf, doc) + print_attributes(buf, attrs) + print_visibility(buf, vis) + buf.write_string("using @") + buf.write_string(pkg.name) + buf.write_string(" {\n") + for pair in names { + let (target, kind) = pair + buf.write_string(" ") + match kind { + Type => buf.write_string("type ") + Trait => buf.write_string("trait ") + Value => () + } + buf.write_string(target.binder.name) + match target.target { + Some(t) => { + buf.write_string(" as ") + buf.write_string(t.name) + } + None => () + } + buf.write_char('\n') + } + buf.write_char('}') + } + } +} + +///| +fn print_alias_targets( + buf : StringBuilder, + targets : @list.List[@syntax.AliasTarget], + is_list : Bool +) -> Unit { + if is_list { + buf.write_char('(') + } + let mut first = true + for target in targets { + if not(first) { + buf.write_string(", ") + } + first = false + // target = original type name, binder = new alias name + // Syntax: typealias OriginalName as AliasName + match target.target { + Some(t) => { + buf.write_string(t.name) + buf.write_string(" as ") + buf.write_string(target.binder.name) + } + None => { + // No alias, just the name + buf.write_string(target.binder.name) + } + } + } + if is_list { + buf.write_char(')') + } +} + +///| +fn print_fun_decl(buf : StringBuilder, decl : @syntax.FunDecl) -> Unit { + print_visibility(buf, decl.vis) + buf.write_string("fn ") + match decl.type_name { + Some(tn) => { + print_type_name(buf, tn) + buf.write_string("::") + } + None => () + } + buf.write_string(decl.name.name) + print_quantifiers(buf, decl.quantifiers) + match decl.decl_params { + Some(params) => { + buf.write_char('(') + print_parameters(buf, params) + buf.write_char(')') + } + None => buf.write_string("()") + } + match decl.return_type { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + print_error_type(buf, decl.error_type) + buf.write_char(' ') +} + +///| +fn print_decl_body(buf : StringBuilder, body : @syntax.DeclBody, indent~ : Int = 0) -> Unit { + match body { + DeclBody(expr~, ..) => { + buf.write_string("{\n") + print_indent(buf, indent + 1) + print_expr(buf, expr, indent=indent + 1) + buf.write_char('\n') + print_indent(buf, indent) + buf.write_char('}') + } + DeclStubs(stubs) => { + match stubs { + Import(module_name~, func_name~, language~) => { + buf.write_string("= \"") + match language { + Some(lang) => { + buf.write_string(lang) + buf.write_string("\" \"") + } + None => () + } + buf.write_string(module_name) + buf.write_string("\" \"") + buf.write_string(func_name) + buf.write_char('"') + } + Embedded(language~, code~) => { + buf.write_string("= ") + match language { + Some(lang) => { + buf.write_char('"') + buf.write_string(lang) + buf.write_string("\" ") + } + None => () + } + match code { + CodeString(s) => { + buf.write_char('"') + buf.write_string(s) + buf.write_char('"') + } + CodeMultilineString(lines) => { + for line in lines { + buf.write_string("#| ") + buf.write_string(line) + buf.write_char('\n') + } + } + } + } + } + } + } +} + +///| +fn print_type_decl(buf : StringBuilder, decl : @syntax.TypeDecl) -> Unit { + print_visibility(buf, decl.type_vis) + match decl.components { + Variant(constrs) => { + buf.write_string("enum ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + buf.write_string(" {\n") + for constr in constrs { + buf.write_string(" ") + buf.write_string(constr.name.name) + match constr.args { + Some(args) if not(args.is_empty()) => { + buf.write_char('(') + print_constr_params(buf, args) + buf.write_char(')') + } + _ => () + } + buf.write_char('\n') + } + buf.write_char('}') + } + Record(fields) => { + buf.write_string("struct ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + buf.write_string(" {\n") + for field in fields { + buf.write_string(" ") + print_visibility(buf, field.vis) + if field.mut_ { + buf.write_string("mut ") + } + buf.write_string(field.name.label) + buf.write_string(" : ") + print_type(buf, field.ty) + buf.write_char('\n') + } + buf.write_char('}') + } + Alias(ty) => { + buf.write_string("typealias ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + buf.write_string(" = ") + print_type(buf, ty) + } + Newtype(ty) => { + buf.write_string("type ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + buf.write_char(' ') + print_type(buf, ty) + } + Abstract => { + buf.write_string("type ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + } + Extern => { + buf.write_string("extern type ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + } + TupleStruct(tys) => { + buf.write_string("struct ") + buf.write_string(decl.tycon) + print_type_params(buf, decl.params) + buf.write_char('(') + print_type_list(buf, tys) + buf.write_char(')') + } + Error(ex_decl) => { + buf.write_string("suberror ") + buf.write_string(decl.tycon) + match ex_decl { + NoPayload => () + SinglePayload(ty) => { + buf.write_char(' ') + print_type(buf, ty) + } + EnumPayload(constrs) => { + buf.write_string(" {\n") + for constr in constrs { + buf.write_string(" ") + buf.write_string(constr.name.name) + match constr.args { + Some(args) if not(args.is_empty()) => { + buf.write_char('(') + print_constr_params(buf, args) + buf.write_char(')') + } + _ => () + } + buf.write_char('\n') + } + buf.write_char('}') + } + } + } + } + if not(decl.deriving.is_empty()) { + buf.write_string(" derive(") + let mut first = true + for d in decl.deriving { + if not(first) { + buf.write_string(", ") + } + first = false + print_type_name(buf, d.type_name) + } + buf.write_char(')') + } +} + +///| +fn print_trait_decl(buf : StringBuilder, decl : @syntax.TraitDecl) -> Unit { + print_visibility(buf, decl.vis) + buf.write_string("trait ") + buf.write_string(decl.name.name) + if not(decl.supers.is_empty()) { + buf.write_string(" : ") + let mut first = true + for super_ in decl.supers { + if not(first) { + buf.write_string(" + ") + } + first = false + print_long_ident(buf, super_.trait_) + } + } + buf.write_string(" {\n") + for m in decl.methods { + buf.write_string(" ") + buf.write_string(m.name.name) + buf.write_char('(') + print_trait_method_params(buf, m.params) + buf.write_char(')') + match m.return_type { + Some(t) => { + buf.write_string(" -> ") + print_type(buf, t) + } + None => () + } + buf.write_char('\n') + } + buf.write_char('}') +} + +///| +fn print_visibility(buf : StringBuilder, vis : @syntax.Visibility) -> Unit { + match vis { + Default => () + Pub(attr~, ..) => { + buf.write_string("pub") + match attr { + Some(a) => { + buf.write_char('(') + buf.write_string(a) + buf.write_char(')') + } + None => () + } + buf.write_char(' ') + } + Priv(..) => buf.write_string("priv ") + } +} + +///| +fn print_quantifiers( + buf : StringBuilder, + quantifiers : @list.List[@syntax.TypeVarBinder] +) -> Unit { + if not(quantifiers.is_empty()) { + buf.write_char('[') + let mut first = true + for q in quantifiers { + if not(first) { + buf.write_string(", ") + } + first = false + buf.write_string(q.name) + if not(q.constraints.is_empty()) { + buf.write_string(" : ") + let mut first_c = true + for c in q.constraints { + if not(first_c) { + buf.write_string(" + ") + } + first_c = false + print_long_ident(buf, c.trait_) + } + } + } + buf.write_char(']') + } +} + +///| +fn print_type_params( + buf : StringBuilder, + params : @list.List[@syntax.TypeDeclBinder] +) -> Unit { + if not(params.is_empty()) { + buf.write_char('[') + let mut first = true + for p in params { + if not(first) { + buf.write_string(", ") + } + first = false + match p.name { + Some(name) => buf.write_string(name) + None => buf.write_char('_') + } + } + buf.write_char(']') + } +} + +///| +fn print_constr_params( + buf : StringBuilder, + params : @list.List[@syntax.ConstrParam] +) -> Unit { + let mut first = true + for p in params { + if not(first) { + buf.write_string(", ") + } + first = false + match p.label { + Some(label) => { + buf.write_string(label.name) + buf.write_string("~ : ") + } + None => () + } + if p.mut_ { + buf.write_string("mut ") + } + print_type(buf, p.ty) + } +} + +///| +/// Print parameters for trait method signatures. +/// In trait methods, DiscardPositional outputs just the type (e.g., Self) +fn print_trait_method_params( + buf : StringBuilder, + params : @list.List[@syntax.Parameter] +) -> Unit { + let mut first = true + for p in params { + if not(first) { + buf.write_string(", ") + } + first = false + match p { + Positional(binder~, ty~) => { + buf.write_string(binder.name) + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + DiscardPositional(ty~, ..) => { + // In trait methods, output just the type (e.g., Self) + match ty { + Some(t) => print_type(buf, t) + None => buf.write_char('_') + } + } + Labelled(binder~, ty~) | Optional(binder~, ty~, ..) => { + buf.write_string(binder.name) + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + _ => () + } + } +} + +///| +fn print_parameters( + buf : StringBuilder, + params : @list.List[@syntax.Parameter] +) -> Unit { + let mut first = true + for p in params { + if not(first) { + buf.write_string(", ") + } + first = false + match p { + Positional(binder~, ty~) => { + buf.write_string(binder.name) + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + DiscardPositional(ty~, ..) => { + buf.write_char('_') + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + Labelled(binder~, ty~) => { + buf.write_string(binder.name) + buf.write_char('~') + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + Optional(binder~, default~, ty~) => { + buf.write_string(binder.name) + buf.write_char('~') + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + buf.write_string(" = ") + print_expr(buf, default) + } + QuestionOptional(binder~, ty~) => { + buf.write_string(binder.name) + buf.write_char('?') + match ty { + Some(t) => { + buf.write_string(" : ") + print_type(buf, t) + } + None => () + } + } + } + } +} + +///| +fn print_error_type(buf : StringBuilder, err_ty : @syntax.ErrorType) -> Unit { + match err_ty { + NoErrorType => () + ErrorType(ty~) => { + buf.write_char('!') + print_type(buf, ty) + } + DefaultErrorType(..) => buf.write_char('!') + Noraise(..) => () + MaybeError(ty~) => { + buf.write_char('?') + print_type(buf, ty) + } + } +} + +///| +/// Print doc string (/// comments) +fn print_doc_string(buf : StringBuilder, doc : @syntax.DocString) -> Unit { + for line in doc.content { + buf.write_string("///") + buf.write_string(line) + buf.write_char('\n') + } +} + +///| +/// Print attributes (#name or #name(...)) +fn print_attributes( + buf : StringBuilder, + attrs : @list.List[@attribute.Attribute] +) -> Unit { + for attr in attrs { + buf.write_string(attr.raw) + buf.write_char('\n') + } +} diff --git a/printer/printer_test.mbt b/printer/printer_test.mbt new file mode 100644 index 0000000..880410e --- /dev/null +++ b/printer/printer_test.mbt @@ -0,0 +1,285 @@ +///| +/// Round-trip test helper: parse → print → parse → print +/// The two print results should be identical. +fn assert_roundtrip(source : String) -> Unit! { + let (impls1, errors1) = @moonbitlang/parser.parse_string(source) + assert_eq!(errors1.length(), 0) + let printed1 = print_impls(impls1) + let (impls2, errors2) = @moonbitlang/parser.parse_string(printed1) + assert_eq!(errors2.length(), 0) + let printed2 = print_impls(impls2) + assert_eq!(printed1, printed2) +} + +///| +test "print simple function" { + let source = + #|fn add(a : Int, b : Int) -> Int { + #| a + b + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="fn add(a : Int, b : Int) -> Int {\n\ta + b\n}\n", + ) +} + +///| +test "print struct" { + let source = + #|struct Point { + #| x : Int + #| y : Int + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="struct Point {\n x : Int\n y : Int\n}\n", + ) +} + +///| +test "print enum" { + let source = + #|enum Color { + #| Red + #| Green + #| Blue + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="enum Color {\n Red\n Green\n Blue\n}\n", + ) +} + +///| +test "print let binding" { + let source = + #|let x : Int = 42 + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect(result, content="let x : Int = 42\n") +} + +///| +test "print match expression" { + let source = + #|fn describe(n : Int) -> String { + #| match n { + #| 0 => "zero" + #| _ => "other" + #| } + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="fn describe(n : Int) -> String {\n\tmatch n {\n\t\t0 => \"zero\"\n\t\t_ => \"other\"\n\t}\n}\n", + ) +} + +///| +test "print operator precedence" { + let source = + #|fn calc() -> Int { + #| 1 + 2 * 3 + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="fn calc() -> Int {\n\t1 + 2 * 3\n}\n", + ) +} + +///| +test "print deprecated attribute" { + let source = + #|#deprecated("Use bar instead") + #|fn foo() -> Unit { + #| () + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="#deprecated(\"Use bar instead\")\nfn foo() -> Unit {\n\t()\n}\n", + ) +} + +///| +test "print doc comment" { + let source = + #|/// This is a doc comment. + #|fn documented() -> Unit { + #| () + #|} + let (impls, errors) = @moonbitlang/parser.parse_string(source) + assert_eq(errors.length(), 0) + let result = print_impls(impls) + inspect( + result, + content="/// This is a doc comment.\nfn documented() -> Unit {\n\t()\n}\n", + ) +} + +///| +test "roundtrip: simple function" { + assert_roundtrip!( + #|fn add(a : Int, b : Int) -> Int { + #| a + b + #|} + ) +} + +///| +test "roundtrip: struct" { + assert_roundtrip!( + #|struct Point { + #| x : Int + #| y : Int + #|} + ) +} + +///| +test "roundtrip: enum" { + assert_roundtrip!( + #|enum Color { + #| Red + #| Green + #| Blue(Int) + #|} + ) +} + +///| +test "roundtrip: match expression" { + assert_roundtrip!( + #|fn describe(n : Int) -> String { + #| match n { + #| 0 => "zero" + #| 1 | 2 => "small" + #| _ => "other" + #| } + #|} + ) +} + +///| +test "roundtrip: if expression" { + assert_roundtrip!( + #|fn max(a : Int, b : Int) -> Int { + #| if a > b { a } else { b } + #|} + ) +} + +///| +test "roundtrip: while loop" { + assert_roundtrip!( + #|fn count() -> Unit { + #| let mut i = 0 + #| while i < 10 { + #| i = i + 1 + #| } + #|} + ) +} + +///| +test "roundtrip: for loop" { + assert_roundtrip!( + #|fn sum(arr : Array[Int]) -> Int { + #| let mut total = 0 + #| for i in arr { + #| total = total + i + #| } + #| total + #|} + ) +} + +///| +test "roundtrip: trait" { + assert_roundtrip!( + #|trait Show { + #| show(Self) -> String + #|} + ) +} + +///| +test "roundtrip: method call" { + assert_roundtrip!( + #|fn process(s : String) -> Int { + #| s.length() + #|} + ) +} + +///| +test "roundtrip: array and tuple" { + assert_roundtrip!( + #|fn make_pair() -> (Int, String) { + #| (1, "hello") + #|} + ) +} + +///| +test "roundtrip: record pattern" { + assert_roundtrip!( + #|fn get_x(p : Point) -> Int { + #| match p { + #| { x, .. } => x + #| } + #|} + ) +} + +///| +test "roundtrip: generic function" { + assert_roundtrip!( + #|fn identity[T](x : T) -> T { + #| x + #|} + ) +} + +///| +test "roundtrip: impl trait" { + assert_roundtrip!( + #|impl Show for Point with output(self, logger) { + #| logger.write_string("Point") + #|} + ) +} + +///| +test "roundtrip: type alias" { + assert_roundtrip!( + #|typealias Point as P + ) +} + +///| +test "roundtrip: attribute" { + assert_roundtrip!( + #|#deprecated("Use bar instead") + #|fn foo() -> Unit { + #| () + #|} + ) +} diff --git a/printer/type.mbt b/printer/type.mbt new file mode 100644 index 0000000..97ced36 --- /dev/null +++ b/printer/type.mbt @@ -0,0 +1,92 @@ +///| +/// Print a type expression. +pub fn print_type(buf : StringBuilder, ty : @syntax.Type) -> Unit { + match ty { + Any(..) => buf.write_char('_') + Arrow(args~, res~, err~, is_async~, ..) => { + match is_async { + Some(_) => buf.write_string("async ") + None => () + } + buf.write_char('(') + print_type_list(buf, args) + buf.write_string(") -> ") + print_type(buf, res) + match err { + NoErrorType => () + ErrorType(ty~) => { + buf.write_char('!') + print_type(buf, ty) + } + DefaultErrorType(..) => buf.write_char('!') + Noraise(..) => () + MaybeError(ty~) => { + buf.write_char('?') + print_type(buf, ty) + } + } + } + Tuple(tys~, ..) => { + buf.write_char('(') + print_type_list(buf, tys) + buf.write_char(')') + } + Name(constr_id~, tys~, ..) => { + print_constr_id(buf, constr_id) + if not(tys.is_empty()) { + buf.write_char('[') + print_type_list(buf, tys) + buf.write_char(']') + } + } + Option(ty~, ..) => { + print_type(buf, ty) + buf.write_char('?') + } + Object(constr_id) => { + buf.write_char('&') + print_constr_id(buf, constr_id) + } + } +} + +///| +fn print_type_list( + buf : StringBuilder, + tys : @list.List[@syntax.Type] +) -> Unit { + let mut first = true + for ty in tys { + if not(first) { + buf.write_string(", ") + } + first = false + print_type(buf, ty) + } +} + +///| +fn print_constr_id(buf : StringBuilder, id : @syntax.ConstrId) -> Unit { + print_long_ident(buf, id.id) +} + +///| +fn print_long_ident(buf : StringBuilder, id : @syntax.LongIdent) -> Unit { + match id { + Ident(name~) => buf.write_string(name) + Dot(pkg~, id~) => { + buf.write_char('@') + buf.write_string(pkg) + buf.write_char('.') + buf.write_string(id) + } + } +} + +///| +fn print_type_name(buf : StringBuilder, tn : @syntax.TypeName) -> Unit { + if tn.is_object { + buf.write_char('&') + } + print_long_ident(buf, tn.name) +} From 98d918e7a253423609efbf53d65b6bf2bacaec37 Mon Sep 17 00:00:00 2001 From: mizchi Date: Fri, 16 Jan 2026 00:46:42 +0900 Subject: [PATCH 2/3] chore: apply moon fmt and generate pkg.mbti Co-Authored-By: Claude Opus 4.5 --- printer/expr.mbt | 32 +++--- printer/pattern.mbt | 9 +- printer/pkg.generated.mbti | 27 +++++ printer/printer.mbt | 43 ++++---- printer/printer_test.mbt | 208 ++++++++++++++++++++----------------- printer/type.mbt | 5 +- 6 files changed, 183 insertions(+), 141 deletions(-) create mode 100644 printer/pkg.generated.mbti diff --git a/printer/expr.mbt b/printer/expr.mbt index bb1ae11..9cecd67 100644 --- a/printer/expr.mbt +++ b/printer/expr.mbt @@ -37,7 +37,8 @@ fn get_precedence(op : String) -> Int { /// Check if expression needs parentheses in a binary context fn needs_parens(expr : @syntax.Expr, parent_prec : Int) -> Bool { match expr { - Infix(op~, ..) => get_precedence(long_ident_to_string(op.name)) < parent_prec + Infix(op~, ..) => + get_precedence(long_ident_to_string(op.name)) < parent_prec _ => false } } @@ -48,7 +49,7 @@ fn print_expr_with_prec( buf : StringBuilder, expr : @syntax.Expr, parent_prec : Int, - indent : Int + indent : Int, ) -> Unit { if needs_parens(expr, parent_prec) { buf.write_char('(') @@ -61,7 +62,11 @@ fn print_expr_with_prec( ///| /// Print an expression. -pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) -> Unit { +pub fn print_expr( + buf : StringBuilder, + expr : @syntax.Expr, + indent? : Int = 0, +) -> Unit { match expr { // Literals Constant(c~, ..) => print_constant(buf, c) @@ -375,7 +380,7 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - } // Functions - Function(func~, ..) => { + Function(func~, ..) => match func.kind { Lambda => { buf.write_string("fn(") @@ -402,7 +407,6 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - print_expr(buf, func.body, indent~) } } - } // Let bindings Let(pattern~, expr~, body~, ..) => { @@ -754,13 +758,12 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - } // Hole - Hole(kind~, ..) => { + Hole(kind~, ..) => match kind { Synthesized => buf.write_char('_') Incomplete => buf.write_string("...") Todo => buf.write_string("todo") } - } // Map Map(elems~, ..) => { @@ -779,7 +782,7 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - } // Group - Group(expr~, group~, ..) => { + Group(expr~, group~, ..) => match group { Paren => { buf.write_char('(') @@ -792,7 +795,6 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - buf.write_string("\n}") } } - } // String interpolation Interp(elems~, ..) => { @@ -810,7 +812,7 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - } buf.write_char('"') } - MultilineString(elems~, ..) => { + MultilineString(elems~, ..) => for e in elems { match e { String(s) => { @@ -835,7 +837,6 @@ pub fn print_expr(buf : StringBuilder, expr : @syntax.Expr, indent~ : Int = 0) - } } } - } // LexMatch LexMatch(strategy~, expr~, cases~, ..) => { @@ -886,7 +887,7 @@ fn print_var(buf : StringBuilder, v : @syntax.Var) -> Unit { ///| fn print_arguments( buf : StringBuilder, - args : @list.List[@syntax.Argument] + args : @list.List[@syntax.Argument], ) -> Unit { let mut first = true for arg in args { @@ -922,7 +923,7 @@ fn print_arguments( ///| fn print_lex_top_patterns( buf : StringBuilder, - pats : @list.List[@syntax.LexTopPattern] + pats : @list.List[@syntax.LexTopPattern], ) -> Unit { let mut first = true for p in pats { @@ -935,7 +936,10 @@ fn print_lex_top_patterns( } ///| -fn print_lex_top_pattern(buf : StringBuilder, pat : @syntax.LexTopPattern) -> Unit { +fn print_lex_top_pattern( + buf : StringBuilder, + pat : @syntax.LexTopPattern, +) -> Unit { match pat { Pattern(lex_pat) => print_lex_pattern(buf, lex_pat) Binder(b) => buf.write_string(b.name) diff --git a/printer/pattern.mbt b/printer/pattern.mbt index 3b784b8..37af18f 100644 --- a/printer/pattern.mbt +++ b/printer/pattern.mbt @@ -32,10 +32,7 @@ pub fn print_pattern(buf : StringBuilder, pat : @syntax.Pattern) -> Unit { } buf.write_char(')') } - _ => - if is_open { - buf.write_string("(..)") - } + _ => if is_open { buf.write_string("(..)") } } } Or(pat1~, pat2~, ..) => { @@ -123,7 +120,7 @@ pub fn print_pattern(buf : StringBuilder, pat : @syntax.Pattern) -> Unit { ///| fn print_array_patterns( buf : StringBuilder, - pats : @syntax.ArrayPatterns + pats : @syntax.ArrayPatterns, ) -> Unit { match pats { Closed(pats) => { @@ -200,7 +197,7 @@ fn print_array_pattern(buf : StringBuilder, p : @syntax.ArrayPattern) -> Unit { ///| fn print_constr_pat_args( buf : StringBuilder, - args : @list.List[@syntax.ConstrPatArg] + args : @list.List[@syntax.ConstrPatArg], ) -> Unit { let mut first = true for arg in args { diff --git a/printer/pkg.generated.mbti b/printer/pkg.generated.mbti new file mode 100644 index 0000000..4adf83b --- /dev/null +++ b/printer/pkg.generated.mbti @@ -0,0 +1,27 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/printer" + +import( + "moonbitlang/core/list" + "moonbitlang/parser/syntax" +) + +// Values +pub fn print_constant(StringBuilder, @syntax.Constant) -> Unit + +pub fn print_expr(StringBuilder, @syntax.Expr, indent? : Int) -> Unit + +pub fn print_impls(@list.List[@syntax.Impl]) -> String + +pub fn print_pattern(StringBuilder, @syntax.Pattern) -> Unit + +pub fn print_type(StringBuilder, @syntax.Type) -> Unit + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/printer/printer.mbt b/printer/printer.mbt index 20868b2..7f07eaa 100644 --- a/printer/printer.mbt +++ b/printer/printer.mbt @@ -80,7 +80,8 @@ fn print_impl(buf : StringBuilder, impl_ : @syntax.Impl) -> Unit { body~, attrs~, doc~, - ..) => { + .. + ) => { print_doc_string(buf, doc) print_attributes(buf, attrs) buf.write_string("impl ") @@ -129,10 +130,7 @@ fn print_impl(buf : StringBuilder, impl_ : @syntax.Impl) -> Unit { buf.write_string(tn.name) buf.write_string("::") } - None => - if pkg is Some(_) { - buf.write_char('.') - } + None => if pkg is Some(_) { buf.write_char('.') } } print_alias_targets(buf, targets, is_list) } @@ -177,7 +175,8 @@ fn print_impl(buf : StringBuilder, impl_ : @syntax.Impl) -> Unit { vis~, attrs~, doc~, - ..) => { + .. + ) => { print_doc_string(buf, doc) print_attributes(buf, attrs) print_visibility(buf, vis) @@ -252,7 +251,7 @@ fn print_impl(buf : StringBuilder, impl_ : @syntax.Impl) -> Unit { fn print_alias_targets( buf : StringBuilder, targets : @list.List[@syntax.AliasTarget], - is_list : Bool + is_list : Bool, ) -> Unit { if is_list { buf.write_char('(') @@ -271,10 +270,9 @@ fn print_alias_targets( buf.write_string(" as ") buf.write_string(target.binder.name) } - None => { + None => // No alias, just the name buf.write_string(target.binder.name) - } } } if is_list { @@ -315,7 +313,11 @@ fn print_fun_decl(buf : StringBuilder, decl : @syntax.FunDecl) -> Unit { } ///| -fn print_decl_body(buf : StringBuilder, body : @syntax.DeclBody, indent~ : Int = 0) -> Unit { +fn print_decl_body( + buf : StringBuilder, + body : @syntax.DeclBody, + indent? : Int = 0, +) -> Unit { match body { DeclBody(expr~, ..) => { buf.write_string("{\n") @@ -325,7 +327,7 @@ fn print_decl_body(buf : StringBuilder, body : @syntax.DeclBody, indent~ : Int = print_indent(buf, indent) buf.write_char('}') } - DeclStubs(stubs) => { + DeclStubs(stubs) => match stubs { Import(module_name~, func_name~, language~) => { buf.write_string("= \"") @@ -357,17 +359,15 @@ fn print_decl_body(buf : StringBuilder, body : @syntax.DeclBody, indent~ : Int = buf.write_string(s) buf.write_char('"') } - CodeMultilineString(lines) => { + CodeMultilineString(lines) => for line in lines { buf.write_string("#| ") buf.write_string(line) buf.write_char('\n') } - } } } } - } } } @@ -546,7 +546,7 @@ fn print_visibility(buf : StringBuilder, vis : @syntax.Visibility) -> Unit { ///| fn print_quantifiers( buf : StringBuilder, - quantifiers : @list.List[@syntax.TypeVarBinder] + quantifiers : @list.List[@syntax.TypeVarBinder], ) -> Unit { if not(quantifiers.is_empty()) { buf.write_char('[') @@ -576,7 +576,7 @@ fn print_quantifiers( ///| fn print_type_params( buf : StringBuilder, - params : @list.List[@syntax.TypeDeclBinder] + params : @list.List[@syntax.TypeDeclBinder], ) -> Unit { if not(params.is_empty()) { buf.write_char('[') @@ -598,7 +598,7 @@ fn print_type_params( ///| fn print_constr_params( buf : StringBuilder, - params : @list.List[@syntax.ConstrParam] + params : @list.List[@syntax.ConstrParam], ) -> Unit { let mut first = true for p in params { @@ -625,7 +625,7 @@ fn print_constr_params( /// In trait methods, DiscardPositional outputs just the type (e.g., Self) fn print_trait_method_params( buf : StringBuilder, - params : @list.List[@syntax.Parameter] + params : @list.List[@syntax.Parameter], ) -> Unit { let mut first = true for p in params { @@ -644,13 +644,12 @@ fn print_trait_method_params( None => () } } - DiscardPositional(ty~, ..) => { + DiscardPositional(ty~, ..) => // In trait methods, output just the type (e.g., Self) match ty { Some(t) => print_type(buf, t) None => buf.write_char('_') } - } Labelled(binder~, ty~) | Optional(binder~, ty~, ..) => { buf.write_string(binder.name) match ty { @@ -669,7 +668,7 @@ fn print_trait_method_params( ///| fn print_parameters( buf : StringBuilder, - params : @list.List[@syntax.Parameter] + params : @list.List[@syntax.Parameter], ) -> Unit { let mut first = true for p in params { @@ -768,7 +767,7 @@ fn print_doc_string(buf : StringBuilder, doc : @syntax.DocString) -> Unit { /// Print attributes (#name or #name(...)) fn print_attributes( buf : StringBuilder, - attrs : @list.List[@attribute.Attribute] + attrs : @list.List[@attribute.Attribute], ) -> Unit { for attr in attrs { buf.write_string(attr.raw) diff --git a/printer/printer_test.mbt b/printer/printer_test.mbt index 880410e..09eef63 100644 --- a/printer/printer_test.mbt +++ b/printer/printer_test.mbt @@ -1,14 +1,14 @@ ///| /// Round-trip test helper: parse → print → parse → print /// The two print results should be identical. -fn assert_roundtrip(source : String) -> Unit! { +fn assert_roundtrip(source : String) -> Unit raise { let (impls1, errors1) = @moonbitlang/parser.parse_string(source) - assert_eq!(errors1.length(), 0) + assert_eq(errors1.length(), 0) let printed1 = print_impls(impls1) let (impls2, errors2) = @moonbitlang/parser.parse_string(printed1) - assert_eq!(errors2.length(), 0) + assert_eq(errors2.length(), 0) let printed2 = print_impls(impls2) - assert_eq!(printed1, printed2) + assert_eq(printed1, printed2) } ///| @@ -20,10 +20,7 @@ test "print simple function" { let (impls, errors) = @moonbitlang/parser.parse_string(source) assert_eq(errors.length(), 0) let result = print_impls(impls) - inspect( - result, - content="fn add(a : Int, b : Int) -> Int {\n\ta + b\n}\n", - ) + inspect(result, content="fn add(a : Int, b : Int) -> Int {\n\ta + b\n}\n") } ///| @@ -36,10 +33,7 @@ test "print struct" { let (impls, errors) = @moonbitlang/parser.parse_string(source) assert_eq(errors.length(), 0) let result = print_impls(impls) - inspect( - result, - content="struct Point {\n x : Int\n y : Int\n}\n", - ) + inspect(result, content="struct Point {\n x : Int\n y : Int\n}\n") } ///| @@ -53,10 +47,7 @@ test "print enum" { let (impls, errors) = @moonbitlang/parser.parse_string(source) assert_eq(errors.length(), 0) let result = print_impls(impls) - inspect( - result, - content="enum Color {\n Red\n Green\n Blue\n}\n", - ) + inspect(result, content="enum Color {\n Red\n Green\n Blue\n}\n") } ///| @@ -96,10 +87,7 @@ test "print operator precedence" { let (impls, errors) = @moonbitlang/parser.parse_string(source) assert_eq(errors.length(), 0) let result = print_impls(impls) - inspect( - result, - content="fn calc() -> Int {\n\t1 + 2 * 3\n}\n", - ) + inspect(result, content="fn calc() -> Int {\n\t1 + 2 * 3\n}\n") } ///| @@ -136,150 +124,180 @@ test "print doc comment" { ///| test "roundtrip: simple function" { - assert_roundtrip!( - #|fn add(a : Int, b : Int) -> Int { - #| a + b - #|} + assert_roundtrip( + ( + #|fn add(a : Int, b : Int) -> Int { + #| a + b + #|} + ), ) } ///| test "roundtrip: struct" { - assert_roundtrip!( - #|struct Point { - #| x : Int - #| y : Int - #|} + assert_roundtrip( + ( + #|struct Point { + #| x : Int + #| y : Int + #|} + ), ) } ///| test "roundtrip: enum" { - assert_roundtrip!( - #|enum Color { - #| Red - #| Green - #| Blue(Int) - #|} + assert_roundtrip( + ( + #|enum Color { + #| Red + #| Green + #| Blue(Int) + #|} + ), ) } ///| test "roundtrip: match expression" { - assert_roundtrip!( - #|fn describe(n : Int) -> String { - #| match n { - #| 0 => "zero" - #| 1 | 2 => "small" - #| _ => "other" - #| } - #|} + assert_roundtrip( + ( + #|fn describe(n : Int) -> String { + #| match n { + #| 0 => "zero" + #| 1 | 2 => "small" + #| _ => "other" + #| } + #|} + ), ) } ///| test "roundtrip: if expression" { - assert_roundtrip!( - #|fn max(a : Int, b : Int) -> Int { - #| if a > b { a } else { b } - #|} + assert_roundtrip( + ( + #|fn max(a : Int, b : Int) -> Int { + #| if a > b { a } else { b } + #|} + ), ) } ///| test "roundtrip: while loop" { - assert_roundtrip!( - #|fn count() -> Unit { - #| let mut i = 0 - #| while i < 10 { - #| i = i + 1 - #| } - #|} + assert_roundtrip( + ( + #|fn count() -> Unit { + #| let mut i = 0 + #| while i < 10 { + #| i = i + 1 + #| } + #|} + ), ) } ///| test "roundtrip: for loop" { - assert_roundtrip!( - #|fn sum(arr : Array[Int]) -> Int { - #| let mut total = 0 - #| for i in arr { - #| total = total + i - #| } - #| total - #|} + assert_roundtrip( + ( + #|fn sum(arr : Array[Int]) -> Int { + #| let mut total = 0 + #| for i in arr { + #| total = total + i + #| } + #| total + #|} + ), ) } ///| test "roundtrip: trait" { - assert_roundtrip!( - #|trait Show { - #| show(Self) -> String - #|} + assert_roundtrip( + ( + #|trait Show { + #| show(Self) -> String + #|} + ), ) } ///| test "roundtrip: method call" { - assert_roundtrip!( - #|fn process(s : String) -> Int { - #| s.length() - #|} + assert_roundtrip( + ( + #|fn process(s : String) -> Int { + #| s.length() + #|} + ), ) } ///| test "roundtrip: array and tuple" { - assert_roundtrip!( - #|fn make_pair() -> (Int, String) { - #| (1, "hello") - #|} + assert_roundtrip( + ( + #|fn make_pair() -> (Int, String) { + #| (1, "hello") + #|} + ), ) } ///| test "roundtrip: record pattern" { - assert_roundtrip!( - #|fn get_x(p : Point) -> Int { - #| match p { - #| { x, .. } => x - #| } - #|} + assert_roundtrip( + ( + #|fn get_x(p : Point) -> Int { + #| match p { + #| { x, .. } => x + #| } + #|} + ), ) } ///| test "roundtrip: generic function" { - assert_roundtrip!( - #|fn identity[T](x : T) -> T { - #| x - #|} + assert_roundtrip( + ( + #|fn identity[T](x : T) -> T { + #| x + #|} + ), ) } ///| test "roundtrip: impl trait" { - assert_roundtrip!( - #|impl Show for Point with output(self, logger) { - #| logger.write_string("Point") - #|} + assert_roundtrip( + ( + #|impl Show for Point with output(self, logger) { + #| logger.write_string("Point") + #|} + ), ) } ///| test "roundtrip: type alias" { - assert_roundtrip!( - #|typealias Point as P + assert_roundtrip( + ( + #|typealias Point as P + ), ) } ///| test "roundtrip: attribute" { - assert_roundtrip!( - #|#deprecated("Use bar instead") - #|fn foo() -> Unit { - #| () - #|} + assert_roundtrip( + ( + #|#deprecated("Use bar instead") + #|fn foo() -> Unit { + #| () + #|} + ), ) } diff --git a/printer/type.mbt b/printer/type.mbt index 97ced36..be7752b 100644 --- a/printer/type.mbt +++ b/printer/type.mbt @@ -51,10 +51,7 @@ pub fn print_type(buf : StringBuilder, ty : @syntax.Type) -> Unit { } ///| -fn print_type_list( - buf : StringBuilder, - tys : @list.List[@syntax.Type] -) -> Unit { +fn print_type_list(buf : StringBuilder, tys : @list.List[@syntax.Type]) -> Unit { let mut first = true for ty in tys { if not(first) { From 4bd536c1d239c0de7c71eb58317899c2d780f513 Mon Sep 17 00:00:00 2001 From: mizchi Date: Fri, 16 Jan 2026 00:51:05 +0900 Subject: [PATCH 3/3] feat: add print_code convenience function Add print_code(parsed) that takes parse_string result directly and returns None if there are parse errors. Co-Authored-By: Claude Opus 4.5 --- printer/moon.pkg | 1 + printer/pkg.generated.mbti | 3 +++ printer/printer.mbt | 12 ++++++++++++ printer/printer_test.mbt | 17 +++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/printer/moon.pkg b/printer/moon.pkg index 45d6484..27641d0 100644 --- a/printer/moon.pkg +++ b/printer/moon.pkg @@ -3,4 +3,5 @@ import { "moonbitlang/parser/tokens", "moonbitlang/parser", "moonbitlang/parser/attribute", + "moonbitlang/parser/basic", } diff --git a/printer/pkg.generated.mbti b/printer/pkg.generated.mbti index 4adf83b..2b5df10 100644 --- a/printer/pkg.generated.mbti +++ b/printer/pkg.generated.mbti @@ -3,10 +3,13 @@ package "moonbitlang/parser/printer" import( "moonbitlang/core/list" + "moonbitlang/parser/basic" "moonbitlang/parser/syntax" ) // Values +pub fn print_code((@list.List[@syntax.Impl], Array[@basic.Report])) -> String? + pub fn print_constant(StringBuilder, @syntax.Constant) -> Unit pub fn print_expr(StringBuilder, @syntax.Expr, indent? : Int) -> Unit diff --git a/printer/printer.mbt b/printer/printer.mbt index 7f07eaa..18cc405 100644 --- a/printer/printer.mbt +++ b/printer/printer.mbt @@ -2,6 +2,18 @@ /// A simple pretty printer for MoonBit AST. /// This module provides functions to convert AST nodes back to MoonBit source code. +///| +/// Print parsed code back to a string. +/// This is a convenience function that takes the result of parse_string directly. +/// Returns None if there are parse errors. +pub fn print_code(parsed : (@syntax.Impls, Array[@basic.Report])) -> String? { + let (impls, errors) = parsed + if errors.length() > 0 { + return None + } + Some(print_impls(impls)) +} + ///| /// Print a list of top-level implementations to a string. pub fn print_impls(impls : @syntax.Impls) -> String { diff --git a/printer/printer_test.mbt b/printer/printer_test.mbt index 09eef63..bc2c9a0 100644 --- a/printer/printer_test.mbt +++ b/printer/printer_test.mbt @@ -11,6 +11,23 @@ fn assert_roundtrip(source : String) -> Unit raise { assert_eq(printed1, printed2) } +///| +test "print_code with valid source" { + let source = + #|fn hello() -> String { + #| "hello" + #|} + let result = print_code(@moonbitlang/parser.parse_string(source)) + assert_true(result is Some(_)) +} + +///| +test "print_code with parse error" { + let source = "fn invalid { {" + let result = print_code(@moonbitlang/parser.parse_string(source)) + assert_true(result is None) +} + ///| test "print simple function" { let source =