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..9cecd67 --- /dev/null +++ b/printer/expr.mbt @@ -0,0 +1,989 @@ +///| +/// 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..27641d0 --- /dev/null +++ b/printer/moon.pkg @@ -0,0 +1,7 @@ +import { + "moonbitlang/parser/syntax", + "moonbitlang/parser/tokens", + "moonbitlang/parser", + "moonbitlang/parser/attribute", + "moonbitlang/parser/basic", +} diff --git a/printer/pattern.mbt b/printer/pattern.mbt new file mode 100644 index 0000000..37af18f --- /dev/null +++ b/printer/pattern.mbt @@ -0,0 +1,248 @@ +///| +/// 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/pkg.generated.mbti b/printer/pkg.generated.mbti new file mode 100644 index 0000000..2b5df10 --- /dev/null +++ b/printer/pkg.generated.mbti @@ -0,0 +1,30 @@ +// Generated using `moon info`, DON'T EDIT IT +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 + +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 new file mode 100644 index 0000000..18cc405 --- /dev/null +++ b/printer/printer.mbt @@ -0,0 +1,788 @@ +///| +/// 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 { + 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..bc2c9a0 --- /dev/null +++ b/printer/printer_test.mbt @@ -0,0 +1,320 @@ +///| +/// Round-trip test helper: parse → print → parse → print +/// The two print results should be identical. +fn assert_roundtrip(source : String) -> Unit raise { + 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_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 = + #|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..be7752b --- /dev/null +++ b/printer/type.mbt @@ -0,0 +1,89 @@ +///| +/// 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) +}