///|
/// The canonical dump of a tree.
///
/// The format exists so that it can be implemented TWICE -- here and in
/// `tools/pyast_dump.py` over CPython's `ast` -- and the two compared over
/// every source in the suite. So it is defined precisely and kept dull:
///
/// * One node per line, indented two spaces per level of nesting.
/// * A node's line is its kind, then its scalar fields as `name=value`,
/// in the order they are declared here (which is `Python.asdl`'s order).
/// * A field holding a node, an optional node or a list is a line of its
/// own, `name:`, at the next level, with its contents below that. An
/// absent optional is `name: -`; an empty list is `name:` with nothing
/// under it.
/// * Scalars: an identifier bare, an enumerated value by CPython's class
/// name (`Load`, `Add`, `Lt`), a boolean as `True`/`False`, an integer in
/// decimal, a string as Python's `repr`, a float as Python's `repr`, and
/// an absent optional scalar as `-`.
/// * With `pos=true`, a node's line ends with ` @l:c-l:c`, the columns in
/// CODE POINTS. The Python side converts `col_offset` from UTF-8 bytes.
///
/// This is not `ast.dump`'s format. `ast.dump` is a Python expression whose
/// re-parsing is the only way to compare it structurally, and it renders a
/// constant through Python's own `repr` of an arbitrary object. Defining our
/// own costs one Python script and buys a line-oriented diff.
pub fn dump(m : Module, pos? : Bool = false) -> String {
let d = { out: StringBuilder(), pos, }
// `Module`, `arguments`, `comprehension`, `withitem` and `match_case` carry
// no position in CPython's tree, so they carry none here either.
d.out.write_string("Module\n")
d.field_stmts("body", m.body, 1)
d.out.to_string()
}
///|
priv struct Dumper {
out : StringBuilder
pos : Bool
}
///|
fn Dumper::indent(self : Dumper, depth : Int) -> Unit {
for _ in 0.. Unit {
self.indent(depth)
self.out.write_string(kind)
for s in scalars {
self.out.write_string(" \{s.0}=\{s.1}")
}
if self.pos {
self.out.write_string(" @\{span.to_display()}")
}
self.out.write_string("\n")
}
///|
/// A field's introducing line, `name:`, optionally with an inline `-`.
fn Dumper::field(
self : Dumper,
name : String,
depth : Int,
absent? : Bool = false,
) -> Unit {
self.indent(depth)
self.out.write_string(if absent { "\{name}: -\n" } else { "\{name}:\n" })
}
// ---------------------------------------------------------------------------
// Scalars
///|
fn opt_id(s : String?) -> String {
match s {
Some(x) => x
None => "-"
}
}
///|
fn bool_text(b : Bool) -> String {
if b {
"True"
} else {
"False"
}
}
///|
/// A constant, exactly as Python's `repr` writes it.
pub fn Constant::to_literal(self : Constant) -> String {
match self {
Int(n) => n.to_string()
Float(d) => @basic.py_float_repr(d)
Complex(d) => @basic.py_float_layout(d, dot_zero=false) + "j"
Str(s) => @basic.py_repr(s)
Bytes(b) => @basic.py_bytes_repr(b)
Bool(b) => bool_text(b)
None => "None"
Ellipsis => "Ellipsis"
}
}
// ---------------------------------------------------------------------------
// Statements
///|
fn Dumper::field_stmts(
self : Dumper,
name : String,
body : Array[Stmt],
depth : Int,
) -> Unit {
self.field(name, depth)
for s in body {
self.stmt(s, depth + 1)
}
}
///|
fn Dumper::field_exprs(
self : Dumper,
name : String,
items : Array[Expr],
depth : Int,
) -> Unit {
self.field(name, depth)
for e in items {
self.expr(e, depth + 1)
}
}
///|
fn Dumper::field_expr(
self : Dumper,
name : String,
e : Expr,
depth : Int,
) -> Unit {
self.field(name, depth)
self.expr(e, depth + 1)
}
///|
fn Dumper::field_opt_expr(
self : Dumper,
name : String,
e : Expr?,
depth : Int,
) -> Unit {
match e {
None => self.field(name, depth, absent=true)
Some(x) => {
self.field(name, depth)
self.expr(x, depth + 1)
}
}
}
///|
fn Dumper::stmt(self : Dumper, s : Stmt, depth : Int) -> Unit {
let d = depth + 1
match s {
FunctionDef(name~, args~, body~, decorators~, returns~, is_async~, span~) => {
self.node(
"FunctionDef",
[("name", name), ("is_async", bool_text(is_async))],
depth,
span,
)
self.field("args", d)
self.arguments(args, d + 1)
self.field_stmts("body", body, d)
self.field_exprs("decorator_list", decorators, d)
self.field_opt_expr("returns", returns, d)
}
ClassDef(name~, bases~, keywords~, body~, decorators~, span~) => {
self.node("ClassDef", [("name", name)], depth, span)
self.field_exprs("bases", bases, d)
self.field("keywords", d)
for k in keywords {
self.keyword(k, d + 1)
}
self.field_stmts("body", body, d)
self.field_exprs("decorator_list", decorators, d)
}
Return(value~, span~) => {
self.node("Return", [], depth, span)
self.field_opt_expr("value", value, d)
}
Delete(targets~, span~) => {
self.node("Delete", [], depth, span)
self.field_exprs("targets", targets, d)
}
Assign(targets~, value~, span~) => {
self.node("Assign", [], depth, span)
self.field_exprs("targets", targets, d)
self.field_expr("value", value, d)
}
AugAssign(target~, op~, value~, span~) => {
self.node("AugAssign", [("op", op.name())], depth, span)
self.field_expr("target", target, d)
self.field_expr("value", value, d)
}
AnnAssign(target~, annotation~, value~, simple~, span~) => {
self.node("AnnAssign", [("simple", bool_text(simple))], depth, span)
self.field_expr("target", target, d)
self.field_expr("annotation", annotation, d)
self.field_opt_expr("value", value, d)
}
For(target~, iter~, body~, or_else~, is_async~, span~) => {
self.node("For", [("is_async", bool_text(is_async))], depth, span)
self.field_expr("target", target, d)
self.field_expr("iter", iter, d)
self.field_stmts("body", body, d)
self.field_stmts("orelse", or_else, d)
}
While(cond~, body~, or_else~, span~) => {
self.node("While", [], depth, span)
self.field_expr("test", cond, d)
self.field_stmts("body", body, d)
self.field_stmts("orelse", or_else, d)
}
If(cond~, body~, or_else~, span~) => {
self.node("If", [], depth, span)
self.field_expr("test", cond, d)
self.field_stmts("body", body, d)
self.field_stmts("orelse", or_else, d)
}
With(items~, body~, is_async~, span~) => {
self.node("With", [("is_async", bool_text(is_async))], depth, span)
self.field("items", d)
for it in items {
self.with_item(it, d + 1)
}
self.field_stmts("body", body, d)
}
Match(subject~, cases~, span~) => {
self.node("Match", [], depth, span)
self.field_expr("subject", subject, d)
self.field("cases", d)
for c in cases {
self.match_case(c, d + 1)
}
}
Raise(exc~, cause~, span~) => {
self.node("Raise", [], depth, span)
self.field_opt_expr("exc", exc, d)
self.field_opt_expr("cause", cause, d)
}
Try(body~, handlers~, or_else~, finalbody~, is_star~, span~) => {
self.node("Try", [("is_star", bool_text(is_star))], depth, span)
self.field_stmts("body", body, d)
self.field("handlers", d)
for h in handlers {
self.handler(h, d + 1)
}
self.field_stmts("orelse", or_else, d)
self.field_stmts("finalbody", finalbody, d)
}
Assert(cond~, msg~, span~) => {
self.node("Assert", [], depth, span)
self.field_expr("test", cond, d)
self.field_opt_expr("msg", msg, d)
}
Import(names~, span~) => {
self.node("Import", [], depth, span)
self.field("names", d)
for a in names {
self.import_alias(a, d + 1)
}
}
ImportFrom(module_name~, names~, level~, span~) => {
self.node(
"ImportFrom",
[("module", opt_id(module_name)), ("level", level.to_string())],
depth,
span,
)
self.field("names", d)
for a in names {
self.import_alias(a, d + 1)
}
}
Global(names~, span~) =>
self.node("Global", [("names", id_list(names))], depth, span)
Nonlocal(names~, span~) =>
self.node("Nonlocal", [("names", id_list(names))], depth, span)
ExprStmt(value~, span~) => {
self.node("Expr", [], depth, span)
self.field_expr("value", value, d)
}
Pass(span~) => self.node("Pass", [], depth, span)
Break(span~) => self.node("Break", [], depth, span)
Continue(span~) => self.node("Continue", [], depth, span)
}
}
///|
fn id_list(names : Array[String]) -> String {
let out = StringBuilder()
out.write_char('[')
for i, n in names {
if i > 0 {
out.write_char(' ')
}
out.write_string(n)
}
out.write_char(']')
out.to_string()
}
// ---------------------------------------------------------------------------
// Expressions
///|
fn Dumper::expr(self : Dumper, e : Expr, depth : Int) -> Unit {
let d = depth + 1
match e {
BoolOp(op~, values~, span~) => {
self.node("BoolOp", [("op", op.name())], depth, span)
self.field_exprs("values", values, d)
}
NamedExpr(target~, value~, span~) => {
self.node("NamedExpr", [], depth, span)
self.field_expr("target", target, d)
self.field_expr("value", value, d)
}
BinOp(left~, op~, right~, span~) => {
self.node("BinOp", [("op", op.name())], depth, span)
self.field_expr("left", left, d)
self.field_expr("right", right, d)
}
UnaryOp(op~, operand~, span~) => {
self.node("UnaryOp", [("op", op.name())], depth, span)
self.field_expr("operand", operand, d)
}
Lambda(args~, body~, span~) => {
self.node("Lambda", [], depth, span)
self.field("args", d)
self.arguments(args, d + 1)
self.field_expr("body", body, d)
}
IfExp(cond~, body~, or_else~, span~) => {
self.node("IfExp", [], depth, span)
self.field_expr("test", cond, d)
self.field_expr("body", body, d)
self.field_expr("orelse", or_else, d)
}
Dict(keys~, values~, span~) => {
self.node("Dict", [], depth, span)
self.field("keys", d)
for k in keys {
match k {
None => {
self.indent(d + 1)
self.out.write_string("-\n")
}
Some(x) => self.expr(x, d + 1)
}
}
self.field_exprs("values", values, d)
}
Set(elts~, span~) => {
self.node("Set", [], depth, span)
self.field_exprs("elts", elts, d)
}
ListComp(elt~, generators~, span~) => {
self.node("ListComp", [], depth, span)
self.field_expr("elt", elt, d)
self.comprehensions(generators, d)
}
SetComp(elt~, generators~, span~) => {
self.node("SetComp", [], depth, span)
self.field_expr("elt", elt, d)
self.comprehensions(generators, d)
}
DictComp(key~, value~, generators~, span~) => {
self.node("DictComp", [], depth, span)
self.field_expr("key", key, d)
self.field_expr("value", value, d)
self.comprehensions(generators, d)
}
GeneratorExp(elt~, generators~, span~) => {
self.node("GeneratorExp", [], depth, span)
self.field_expr("elt", elt, d)
self.comprehensions(generators, d)
}
Await(value~, span~) => {
self.node("Await", [], depth, span)
self.field_expr("value", value, d)
}
Yield(value~, span~) => {
self.node("Yield", [], depth, span)
self.field_opt_expr("value", value, d)
}
YieldFrom(value~, span~) => {
self.node("YieldFrom", [], depth, span)
self.field_expr("value", value, d)
}
Compare(left~, ops~, comparators~, span~) => {
self.node("Compare", [("ops", op_list(ops))], depth, span)
self.field_expr("left", left, d)
self.field_exprs("comparators", comparators, d)
}
Call(func~, args~, keywords~, span~) => {
self.node("Call", [], depth, span)
self.field_expr("func", func, d)
self.field_exprs("args", args, d)
self.field("keywords", d)
for k in keywords {
self.keyword(k, d + 1)
}
}
JoinedStr(raw~, span~) =>
self.node("JoinedStr", [("raw", @basic.py_repr(raw))], depth, span)
Constant(value~, span~) =>
self.node("Constant", [("value", value.to_literal())], depth, span)
Attribute(value~, attr~, ctx~, span~) => {
self.node("Attribute", [("attr", attr), ("ctx", ctx.name())], depth, span)
self.field_expr("value", value, d)
}
Subscript(value~, slice~, ctx~, span~) => {
self.node("Subscript", [("ctx", ctx.name())], depth, span)
self.field_expr("value", value, d)
self.field_expr("slice", slice, d)
}
Starred(value~, ctx~, span~) => {
self.node("Starred", [("ctx", ctx.name())], depth, span)
self.field_expr("value", value, d)
}
Name(id~, ctx~, span~) =>
self.node("Name", [("id", id), ("ctx", ctx.name())], depth, span)
List(elts~, ctx~, span~) => {
self.node("List", [("ctx", ctx.name())], depth, span)
self.field_exprs("elts", elts, d)
}
Tuple(elts~, ctx~, span~) => {
self.node("Tuple", [("ctx", ctx.name())], depth, span)
self.field_exprs("elts", elts, d)
}
Slice(lower~, upper~, step~, span~) => {
self.node("Slice", [], depth, span)
self.field_opt_expr("lower", lower, d)
self.field_opt_expr("upper", upper, d)
self.field_opt_expr("step", step, d)
}
}
}
///|
fn op_list(ops : Array[CmpOp]) -> String {
let out = StringBuilder()
out.write_char('[')
for i, o in ops {
if i > 0 {
out.write_char(' ')
}
out.write_string(o.name())
}
out.write_char(']')
out.to_string()
}
// ---------------------------------------------------------------------------
// The helper nodes
///|
fn Dumper::comprehensions(
self : Dumper,
gens : Array[Comprehension],
depth : Int,
) -> Unit {
self.field("generators", depth)
for g in gens {
self.indent(depth + 1)
self.out.write_string("comprehension is_async=\{bool_text(g.is_async)}\n")
self.field_expr("target", g.target, depth + 2)
self.field_expr("iter", g.iter, depth + 2)
self.field_exprs("ifs", g.ifs, depth + 2)
}
}
///|
fn Dumper::arguments(self : Dumper, a : Arguments, depth : Int) -> Unit {
self.indent(depth)
self.out.write_string("arguments\n")
let d = depth + 1
self.field("posonlyargs", d)
for x in a.posonlyargs {
self.arg(x, d + 1)
}
self.field("args", d)
for x in a.args {
self.arg(x, d + 1)
}
match a.vararg {
None => self.field("vararg", d, absent=true)
Some(x) => {
self.field("vararg", d)
self.arg(x, d + 1)
}
}
self.field("kwonlyargs", d)
for x in a.kwonlyargs {
self.arg(x, d + 1)
}
self.field("kw_defaults", d)
for x in a.kw_defaults {
match x {
None => {
self.indent(d + 1)
self.out.write_string("-\n")
}
Some(e) => self.expr(e, d + 1)
}
}
match a.kwarg {
None => self.field("kwarg", d, absent=true)
Some(x) => {
self.field("kwarg", d)
self.arg(x, d + 1)
}
}
self.field_exprs("defaults", a.defaults, d)
}
///|
fn Dumper::arg(self : Dumper, a : Arg, depth : Int) -> Unit {
self.node("arg", [("arg", a.arg)], depth, a.span)
self.field_opt_expr("annotation", a.annotation, depth + 1)
}
///|
fn Dumper::keyword(self : Dumper, k : Keyword, depth : Int) -> Unit {
self.node("keyword", [("arg", opt_id(k.arg))], depth, k.span)
self.field_expr("value", k.value, depth + 1)
}
///|
fn Dumper::import_alias(self : Dumper, a : Alias, depth : Int) -> Unit {
self.node(
"alias",
[("name", a.name), ("asname", opt_id(a.asname))],
depth,
a.span,
)
}
///|
fn Dumper::with_item(self : Dumper, w : WithItem, depth : Int) -> Unit {
self.indent(depth)
self.out.write_string("withitem\n")
self.field_expr("context_expr", w.context_expr, depth + 1)
self.field_opt_expr("optional_vars", w.optional_vars, depth + 1)
}
///|
fn Dumper::handler(self : Dumper, h : ExceptHandler, depth : Int) -> Unit {
self.node("ExceptHandler", [("name", opt_id(h.name))], depth, h.span)
self.field_opt_expr("type", h.type_, depth + 1)
self.field_stmts("body", h.body, depth + 1)
}
///|
fn Dumper::match_case(self : Dumper, c : MatchCase, depth : Int) -> Unit {
self.indent(depth)
self.out.write_string("match_case\n")
let d = depth + 1
self.field("pattern", d)
self.pattern(c.pattern, d + 1)
self.field_opt_expr("guard", c.guard_, d)
self.field_stmts("body", c.body, d)
}
// ---------------------------------------------------------------------------
// Patterns
///|
fn Dumper::field_patterns(
self : Dumper,
name : String,
ps : Array[Pattern],
depth : Int,
) -> Unit {
self.field(name, depth)
for p in ps {
self.pattern(p, depth + 1)
}
}
///|
fn Dumper::pattern(self : Dumper, p : Pattern, depth : Int) -> Unit {
let d = depth + 1
match p {
MatchValue(value~, span~) => {
self.node("MatchValue", [], depth, span)
self.field_expr("value", value, d)
}
MatchSingleton(value~, span~) =>
self.node("MatchSingleton", [("value", value.to_literal())], depth, span)
MatchSequence(kind~, patterns~, span~) => {
self.node("MatchSequence", [("kind", kind.name())], depth, span)
self.field_patterns("patterns", patterns, d)
}
MatchMapping(keys~, patterns~, rest~, span~) => {
self.node("MatchMapping", [("rest", opt_id(rest))], depth, span)
self.field_exprs("keys", keys, d)
self.field_patterns("patterns", patterns, d)
}
MatchClass(cls~, patterns~, kwd_attrs~, kwd_patterns~, span~) => {
self.node("MatchClass", [("kwd_attrs", id_list(kwd_attrs))], depth, span)
self.field_expr("cls", cls, d)
self.field_patterns("patterns", patterns, d)
self.field_patterns("kwd_patterns", kwd_patterns, d)
}
MatchStar(name~, span~) =>
self.node("MatchStar", [("name", opt_id(name))], depth, span)
MatchAs(pattern~, name~, span~) => {
self.node("MatchAs", [("name", opt_id(name))], depth, span)
match pattern {
None => self.field("pattern", d, absent=true)
Some(x) => {
self.field("pattern", d)
self.pattern(x, d + 1)
}
}
}
MatchOr(patterns~, span~) => {
self.node("MatchOr", [], depth, span)
self.field_patterns("patterns", patterns, d)
}
}
}