README

#moonbit-community/debug/repr

This package defines Repr : a small, structural, tree-shaped representation used by moonbit-community/debug for:

  • pretty-printing (moonbit-community/debug/pretty_print)
  • diffing (moonbit-community/debug/diff)
  • the Debug trait (moonbit-community/debug)

Repr is exported as a readonly enum ( pub enum Repr ), so it can be pattern-matched outside the package but not directly constructed. Use the smart constructors ( Repr::... ) or the convenience functions ( int , record , ...).

#Records

A record node is represented structurally as:

  • Record([RecordField(name, value), ...])

#Why Record(Array[Repr]) + RecordField ?

You might wonder why we don’t model records more “directly” as something like Record(Array[(String, Repr)]) . The choice here is deliberate:

  • Uniform traversal/rewrite: every tree edge is a Repr. Generic utilities like Repr::children , Repr::with_children , pruning, and the diff algorithm can treat records exactly like arrays/ctors/opaque nodes without special-cases for (String, Repr) pairs.
  • Field names live in the tree: a field is a real node (RecordField(name, ...)), so pretty-printing and diffing can use the same “label + children” pipeline everywhere.
  • Keeps the public API small: an alternative representation would either complicate the children/with_children contract (because record “children” aren’t Repr values) or require additional parallel APIs just for records.

If you want “map-like” semantics (key/value pairs as first-class children), use Map([MapEntry(key, value), ...]) , which is what the Debug instance for Map uses.

So for a value conceptually shaped like:

record { x : Int; y : String }

the corresponding Repr is:

Record([ RecordField("x", Fixnum(...)), RecordField("y", StringLit(...)), ])

#Example (runnable)

///|
test {
let r : Repr = @repr.Repr::record({
"x": Repr::int(1),
"y": Repr::string("hi"),
})
match r {
Record([RecordField("x", Fixnum("1")), RecordField("y", StringLit("hi"))]) =>
()
_ => fail("unexpected Repr shape for record {x: Int; y: String}")
}
}

#Tuples (and unit)

Tuples are represented as:

  • Tuple([a, b, ...])

Unit is the empty tuple:

  • Tuple([]) (also constructible with unit())

#Example (runnable)

///|
test {
let t : Repr = Repr::tuple([Repr::int(1), Repr::string("x")])
match t {
Tuple([Fixnum("1"), StringLit("x")]) => ()
_ => fail("unexpected Repr shape for tuple (Int, String)")
}
}

#Labeled constructor arguments

MoonBit enum variants (and tuple-struct constructors) can have labeled arguments. To preserve those labels in a Repr , use Repr::ctor with optional labels:

  • Repr::ctor("A", [(Some("x"), ...), (Some("y"), ...)]) prints as A(x=..., y=...)

  • you can freely mix positional and labeled args: Repr::ctor("B", [(Some("x"), ...), (None, ...)])

prints as B(x=..., ...)

#Example (runnable)

///|
test {
let r : Repr = Repr::ctor("A", [
(Some("x"), Repr::int(1)),
(Some("y"), Repr::string("hi")),
])
match r {
Enum(
"A",
[EnumLabeledArg("x", Fixnum("1")), EnumLabeledArg("y", StringLit("hi"))]
) => ()
_ => fail("unexpected Repr shape for labeled ctor A(x=Int, y=String)")
}
}

#
Repr

pub enum Repr {
UnitLit
Fixnum(String)
DoubleLit(Double)
FloatLit(Float)
BoolLit(Bool)
CharLit(Char)
StringLit(String)
Tuple(Array[Repr])
Array(Array[Repr])
Record(Array[Repr])
Enum(String, Array[Repr])
Map(Array[Repr])
RecordField(String, Repr)
EnumLabeledArg(String, Repr)
Opaque(String, Array[Repr])
Literal(String)
MapEntry(Repr, Repr)
Omitted
}

Direct structural representation for debugging/diffing/pretty-printing.

pub makes it readonly outside this package: it can be pattern-matched but not directly constructed. Use the smart constructors below.

Design notes:
  • Record is encoded as Record([RecordField(name, value), ...]) (not as an array of (String, Repr) pairs) to keep generic traversal/rewrite code simple: every tree edge is a Repr, so children/with_children, pruning, and diff can work uniformly across node kinds.
  • Labeled enum arguments are encoded as Enum(name, [EnumLabeledArg(label, value), ...]), which supports mixing positional and labeled args.
  • Map is encoded as Map([MapEntry(key, value), ...]), which is intended for "map-like" collections (MoonBit map literals like { k: v }).
  • Opaque is intended for "container-like" wrappers that keep a type/tag but otherwise behave structurally through their children.

#
Repr::array

fn Repr::array(children : Array[Repr]) -> Repr

Construct an Array node from pre-built child Reprs.

#
Repr::bool

fn Repr::bool(x : Bool) -> Repr

Construct a BoolLit leaf.

#
Repr::char

fn Repr::char(x : Char) -> Repr

Construct a CharLit leaf.

#
Repr::children

fn Repr::children(self : Repr) -> Array[Repr]

Child nodes of a Repr node.

Design notes:
  • Leaves return []; container nodes return their stored children.
  • RecordField/EnumLabeledArg have one child; MapEntry has two.
  • children and with_children form a partial lens for tree rewrites: self.with_children(self.children()) == self.

#
Repr::ctor

fn Repr::ctor(name : String, args : Array[(String?, Repr)]) -> Repr

Construct an Enum(name, args) node for enum/constructor applications.

Use None for positional arguments and Some(label) for labeled ones.

#
Repr::dict

fn Repr::dict(contents : Array[(Repr, Repr)]) -> Repr

Construct a Map node from key/value Repr pairs (for map literals).

#
Repr::double

fn Repr::double(x : Double) -> Repr

Construct a DoubleLit leaf.

#
Repr::float

fn Repr::float(x : Float) -> Repr

Construct a FloatLit leaf.

#
Repr::int

fn Repr::int(x : Int) -> Repr

Construct a Fixnum leaf from an Int.

#
Repr::int16

fn Repr::int16(x : Int16) -> Repr

Construct a Fixnum leaf from an Int16.

#
Repr::int64

fn Repr::int64(x : Int64) -> Repr

Construct a Fixnum leaf from an Int64.

#
Repr::literal

fn Repr::literal(value : String) -> Repr

Construct a Literal(value) leaf (already formatted).

#
Repr::omitted

fn Repr::omitted() -> Repr

Construct an Omitted marker node.

#
Repr::opaque_

fn Repr::opaque_(name : String, children : Array[Repr]) -> Repr

Construct an Opaque(name, children) node.

This is useful for values where you want to keep a tag/type name but still show a structural summary through children (e.g. <Map: {...}>).

#
Repr::record

fn Repr::record(fields : Map[String, Repr]) -> Repr

Construct a Record node from pre-built child Reprs.

#
Repr::shallow

fn Repr::shallow(self : Repr) -> Repr

A shallow copy of self containing only its "label" (children replaced).

This is used by diff/pretty-print when the structure is preserved but children are rendered separately.

#
Repr::string

fn Repr::string(x : String) -> Repr

Construct a StringLit leaf.

#
Repr::traverse

fn Repr::traverse(self : Repr, f : (Repr) -> Repr) -> Repr

Traverse a Repr tree and rewrite each node with f.

f runs after children are traversed (post-order), so it sees rewritten children and can hide fields by rewriting RecordField/EnumLabeledArg nodes.

#
Repr::tuple

fn Repr::tuple(children : Array[Repr]) -> Repr

Construct a Tuple node from pre-built child Reprs.

#
Repr::uint

fn Repr::uint(x : UInt) -> Repr

Construct a Fixnum leaf from a UInt.

#
Repr::uint16

fn Repr::uint16(x : UInt16) -> Repr

Construct a Fixnum leaf from a UInt16.

#
Repr::uint64

fn Repr::uint64(x : UInt64) -> Repr

Construct a Fixnum leaf from a UInt64.

#
Repr::unit

fn Repr::unit() -> Repr

#
Repr::with_children

fn Repr::with_children(self : Repr, children : Array[Repr]) -> Repr

Rebuild a Repr node with a new child list (payload is preserved).

Notes:
  • Leaf nodes ignore children and return themselves.
  • RecordField/EnumLabeledArg expect exactly one child and fall back to RecordField(name, Omitted)/EnumLabeledArg(label, Omitted).
  • MapEntry expects exactly two children and falls back to MapEntry(Omitted, Omitted).
  • This is intentionally not a total inverse of children: invalid arity is clamped to keep the tree well-formed for generic traversal.