graphix_compiler/expr/
print.rs

1use crate::expr::{
2    parser, Bind, Expr, ExprKind, Lambda, ModuleKind, Sandbox, SigItem, TypeDef,
3};
4use compact_str::{format_compact, CompactString};
5use netidx::{path::Path, utils::Either};
6use netidx_value::{parser::VAL_ESC, Value};
7use std::fmt::{self, Formatter, Write};
8
9use super::Sig;
10
11impl fmt::Display for TypeDef {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        let Self { name, params, typ } = self;
14        write!(f, "type {name}")?;
15        if !params.is_empty() {
16            write!(f, "<")?;
17            for (i, (tv, ct)) in params.iter().enumerate() {
18                write!(f, "{tv}")?;
19                if let Some(ct) = ct {
20                    write!(f, ": {ct}")?;
21                }
22                if i < params.len() - 1 {
23                    write!(f, ", ")?;
24                }
25            }
26            write!(f, ">")?;
27        }
28        write!(f, " = {typ}")
29    }
30}
31
32impl fmt::Display for Sandbox {
33    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
34        macro_rules! write_sandbox {
35            ($kind:literal, $l:expr) => {{
36                write!(f, "sandbox {} [ ", $kind)?;
37                for (i, p) in $l.iter().enumerate() {
38                    if i < $l.len() - 1 {
39                        write!(f, "{}, ", p)?
40                    } else {
41                        write!(f, "{}", p)?
42                    }
43                }
44                write!(f, " ]")
45            }};
46        }
47        match self {
48            Sandbox::Unrestricted => write!(f, "sandbox unrestricted"),
49            Sandbox::Blacklist(l) => write_sandbox!("blacklist", l),
50            Sandbox::Whitelist(l) => write_sandbox!("whitelist", l),
51        }
52    }
53}
54
55impl fmt::Display for SigItem {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        match self {
58            SigItem::TypeDef(td) => write!(f, "{td}"),
59            SigItem::Bind(name, typ) => write!(f, "val {name}: {typ}"),
60            SigItem::Module(name, sig) => write!(f, "mod {name}: {sig}"),
61        }
62    }
63}
64
65impl fmt::Display for Sig {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "sig {{ ")?;
68        for (i, si) in self.iter().enumerate() {
69            write!(f, "{si}")?;
70            if i < self.len() - 1 {
71                write!(f, "; ")?
72            }
73        }
74        write!(f, " }}")
75    }
76}
77
78fn push_indent(indent: usize, buf: &mut String) {
79    buf.extend((0..indent).into_iter().map(|_| ' '));
80}
81
82fn pretty_print_sandbox(indent: usize, buf: &mut String, s: &Sandbox) -> fmt::Result {
83    macro_rules! write_sandbox {
84        ($kind:literal, $l:expr) => {{
85            writeln!(buf, "sandbox {} [", $kind)?;
86            for (i, p) in $l.iter().enumerate() {
87                push_indent(indent + 4, buf);
88                if i < $l.len() - 1 {
89                    writeln!(buf, "{},", p)?
90                } else {
91                    writeln!(buf, "{}", p)?
92                }
93            }
94            writeln!(buf, "];")
95        }};
96    }
97    push_indent(indent, buf);
98    match s {
99        Sandbox::Unrestricted => writeln!(buf, "sandbox unrestricted;"),
100        Sandbox::Blacklist(l) => write_sandbox!("blacklist", l),
101        Sandbox::Whitelist(l) => write_sandbox!("whitelist", l),
102    }
103}
104
105fn pretty_print_sig_items(
106    indent: usize,
107    buf: &mut String,
108    it: &[SigItem],
109) -> fmt::Result {
110    for (i, si) in it.iter().enumerate() {
111        push_indent(indent, buf);
112        match si {
113            SigItem::TypeDef(td) => write!(buf, "{td}")?,
114            SigItem::Bind(name, typ) => write!(buf, "val {name}: {typ}")?,
115            SigItem::Module(name, sig) => {
116                writeln!(buf, "mod {name}: sig {{")?;
117                pretty_print_sig_items(indent + 2, buf, sig)?;
118                push_indent(indent, buf);
119                write!(buf, "}}")?
120            }
121        }
122        if i < it.len() - 1 {
123            writeln!(buf, ";")?
124        } else {
125            writeln!(buf, "")?
126        }
127    }
128    Ok(())
129}
130
131impl ExprKind {
132    pub fn to_string_pretty(&self, col_limit: usize) -> String {
133        let mut buf = String::new();
134        self.pretty_print(0, col_limit, true, &mut buf).unwrap();
135        buf
136    }
137
138    fn pretty_print(
139        &self,
140        indent: usize,
141        limit: usize,
142        newline: bool,
143        buf: &mut String,
144    ) -> fmt::Result {
145        macro_rules! kill_newline {
146            ($buf:expr) => {
147                if let Some('\n') = $buf.chars().next_back() {
148                    $buf.pop();
149                }
150            };
151        }
152        macro_rules! try_single_line {
153            ($trunc:ident) => {{
154                let len = buf.len();
155                let (start, indent) = if newline {
156                    push_indent(indent, buf);
157                    (len, indent)
158                } else {
159                    (buf.rfind('\n').unwrap_or(0), 0)
160                };
161                writeln!(buf, "{}", self)?;
162                if buf.len() - start <= limit {
163                    return Ok(());
164                } else {
165                    if $trunc {
166                        buf.truncate(len + indent)
167                    }
168                    len + indent
169                }
170            }};
171        }
172        macro_rules! binop {
173            ($sep:literal, $lhs:expr, $rhs:expr) => {{
174                try_single_line!(true);
175                write!(buf, "(")?;
176                writeln!(buf, "{} {}", $lhs, $sep)?;
177                $rhs.kind.pretty_print(indent, limit, true, buf)?;
178                write!(buf, ")")
179            }};
180        }
181        let mut tbuf = CompactString::new("");
182        macro_rules! typ {
183            ($typ:expr) => {{
184                match $typ {
185                    None => "",
186                    Some(typ) => {
187                        tbuf.clear();
188                        write!(tbuf, ": {typ}")?;
189                        tbuf.as_str()
190                    }
191                }
192            }};
193        }
194        fn pretty_print_exprs_int<'a, A, F: Fn(&'a A) -> &'a Expr>(
195            indent: usize,
196            limit: usize,
197            buf: &mut String,
198            exprs: &'a [A],
199            open: &str,
200            close: &str,
201            sep: &str,
202            f: F,
203        ) -> fmt::Result {
204            writeln!(buf, "{}", open)?;
205            for i in 0..exprs.len() {
206                f(&exprs[i]).kind.pretty_print(indent + 2, limit, true, buf)?;
207                if i < exprs.len() - 1 {
208                    kill_newline!(buf);
209                    writeln!(buf, "{}", sep)?
210                }
211            }
212            push_indent(indent, buf);
213            writeln!(buf, "{}", close)
214        }
215        fn pretty_print_exprs(
216            indent: usize,
217            limit: usize,
218            buf: &mut String,
219            exprs: &[Expr],
220            open: &str,
221            close: &str,
222            sep: &str,
223        ) -> fmt::Result {
224            pretty_print_exprs_int(indent, limit, buf, exprs, open, close, sep, |a| a)
225        }
226        let exp = |export| if export { "pub " } else { "" };
227        match self {
228            ExprKind::Constant(_)
229            | ExprKind::Use { .. }
230            | ExprKind::Ref { .. }
231            | ExprKind::StructRef { .. }
232            | ExprKind::TupleRef { .. }
233            | ExprKind::TypeDef { .. }
234            | ExprKind::ArrayRef { .. }
235            | ExprKind::ArraySlice { .. }
236            | ExprKind::StringInterpolate { .. }
237            | ExprKind::Module {
238                name: _,
239                export: _,
240                value: ModuleKind::Unresolved | ModuleKind::Resolved(_),
241            } => {
242                if newline {
243                    push_indent(indent, buf);
244                }
245                writeln!(buf, "{self}")
246            }
247            ExprKind::Bind(b) => {
248                let Bind { doc, pattern, typ, export, value } = &**b;
249                try_single_line!(true);
250                if let Some(doc) = doc {
251                    if doc == "" {
252                        writeln!(buf, "///")?;
253                    } else {
254                        for line in doc.lines() {
255                            writeln!(buf, "///{line}")?;
256                        }
257                    }
258                }
259                writeln!(buf, "{}let {pattern}{} = ", exp(*export), typ!(typ))?;
260                value.kind.pretty_print(indent + 2, limit, false, buf)
261            }
262            ExprKind::StructWith { source, replace } => {
263                try_single_line!(true);
264                match &source.kind {
265                    ExprKind::Ref { .. } => writeln!(buf, "{{ {source} with")?,
266                    _ => writeln!(buf, "{{ ({source}) with")?,
267                }
268                let indent = indent + 2;
269                for (i, (name, e)) in replace.iter().enumerate() {
270                    push_indent(indent, buf);
271                    match &e.kind {
272                        ExprKind::Ref { name: n }
273                            if Path::dirname(&**n).is_none()
274                                && Path::basename(&**n) == Some(&**name) =>
275                        {
276                            write!(buf, "{name}")?
277                        }
278                        e => {
279                            write!(buf, "{name}: ")?;
280                            e.pretty_print(indent + 2, limit, false, buf)?;
281                        }
282                    }
283                    if i < replace.len() - 1 {
284                        kill_newline!(buf);
285                        writeln!(buf, ",")?
286                    }
287                }
288                writeln!(buf, "}}")
289            }
290            ExprKind::Module { name, export, value: ModuleKind::Inline(exprs) } => {
291                try_single_line!(true);
292                write!(buf, "{}mod {name} ", exp(*export))?;
293                pretty_print_exprs(indent, limit, buf, exprs, "{", "}", ";")
294            }
295            ExprKind::Module {
296                name,
297                export,
298                value: ModuleKind::Dynamic { sandbox, sig, source },
299            } => {
300                try_single_line!(true);
301                writeln!(buf, "{}mod {name} dynamic {{", exp(*export))?;
302                pretty_print_sandbox(indent + 2, buf, sandbox)?;
303                push_indent(indent + 2, buf);
304                writeln!(buf, "sig {{")?;
305                pretty_print_sig_items(indent + 2, buf, sig)?;
306                push_indent(indent + 2, buf);
307                writeln!(buf, "}};")?;
308                push_indent(indent + 2, buf);
309                write!(buf, "source ")?;
310                source.kind.pretty_print(indent + 2, limit, false, buf)?;
311                writeln!(buf, "}}")
312            }
313            ExprKind::Do { exprs } => {
314                try_single_line!(true);
315                pretty_print_exprs(indent, limit, buf, exprs, "{", "}", ";")
316            }
317            ExprKind::Connect { name, value, deref } => {
318                try_single_line!(true);
319                let deref = if *deref { "*" } else { "" };
320                writeln!(buf, "{deref}{name} <- ")?;
321                value.kind.pretty_print(indent + 2, limit, false, buf)
322            }
323            ExprKind::TypeCast { expr, typ } => {
324                try_single_line!(true);
325                writeln!(buf, "cast<{typ}>(")?;
326                expr.kind.pretty_print(indent + 2, limit, true, buf)?;
327                writeln!(buf, ")")
328            }
329            ExprKind::Array { args } => {
330                try_single_line!(true);
331                pretty_print_exprs(indent, limit, buf, args, "[", "]", ",")
332            }
333            ExprKind::Any { args } => {
334                try_single_line!(true);
335                write!(buf, "any")?;
336                pretty_print_exprs(indent, limit, buf, args, "(", ")", ",")
337            }
338            ExprKind::Tuple { args } => {
339                try_single_line!(true);
340                pretty_print_exprs(indent, limit, buf, args, "(", ")", ",")
341            }
342            ExprKind::Variant { tag: _, args } if args.len() == 0 => {
343                if newline {
344                    push_indent(indent, buf)
345                }
346                write!(buf, "{self}")
347            }
348            ExprKind::Variant { tag, args } => {
349                try_single_line!(true);
350                write!(buf, "`{tag}")?;
351                pretty_print_exprs(indent, limit, buf, args, "(", ")", ",")
352            }
353            ExprKind::Struct { args } => {
354                try_single_line!(true);
355                writeln!(buf, "{{")?;
356                for (i, (n, e)) in args.iter().enumerate() {
357                    push_indent(indent + 2, buf);
358                    match &e.kind {
359                        ExprKind::Ref { name }
360                            if Path::dirname(&**name).is_none()
361                                && Path::basename(&**name) == Some(&**n) =>
362                        {
363                            write!(buf, "{n}")?
364                        }
365                        _ => {
366                            write!(buf, "{n}: ")?;
367                            e.kind.pretty_print(indent + 2, limit, false, buf)?;
368                        }
369                    }
370                    if i < args.len() - 1 {
371                        kill_newline!(buf);
372                        writeln!(buf, ", ")?
373                    }
374                }
375                push_indent(indent, buf);
376                writeln!(buf, "}}")
377            }
378            ExprKind::Qop(e) => {
379                try_single_line!(true);
380                e.kind.pretty_print(indent, limit, true, buf)?;
381                kill_newline!(buf);
382                writeln!(buf, "?")
383            }
384            ExprKind::Apply { function, args } => {
385                try_single_line!(true);
386                match &function.kind {
387                    ExprKind::Ref { .. } => {
388                        function.kind.pretty_print(indent, limit, true, buf)?
389                    }
390                    e => {
391                        write!(buf, "(")?;
392                        e.pretty_print(indent, limit, true, buf)?;
393                        kill_newline!(buf);
394                        write!(buf, ")")?;
395                    }
396                }
397                kill_newline!(buf);
398                writeln!(buf, "(")?;
399                for i in 0..args.len() {
400                    match &args[i].0 {
401                        None => {
402                            args[i].1.kind.pretty_print(indent + 2, limit, true, buf)?
403                        }
404                        Some(name) => match &args[i].1.kind {
405                            ExprKind::Ref { name: n }
406                                if Path::dirname(&n.0).is_none()
407                                    && Path::basename(&n.0) == Some(name.as_str()) =>
408                            {
409                                writeln!(buf, "#{name}")?
410                            }
411                            _ => {
412                                write!(buf, "#{name}: ")?;
413                                args[i].1.kind.pretty_print(
414                                    indent + 2,
415                                    limit,
416                                    false,
417                                    buf,
418                                )?
419                            }
420                        },
421                    }
422                    if i < args.len() - 1 {
423                        kill_newline!(buf);
424                        writeln!(buf, ",")?
425                    }
426                }
427                writeln!(buf, ")")
428            }
429            ExprKind::Lambda(l) => {
430                let Lambda { args, vargs, rtype, constraints, body } = &**l;
431                try_single_line!(true);
432                for (i, (tvar, typ)) in constraints.iter().enumerate() {
433                    write!(buf, "{tvar}: {typ}")?;
434                    if i < constraints.len() - 1 {
435                        write!(buf, ", ")?;
436                    }
437                }
438                write!(buf, "|")?;
439                for (i, a) in args.iter().enumerate() {
440                    match &a.labeled {
441                        None => {
442                            write!(buf, "{}", a.pattern)?;
443                            buf.push_str(typ!(&a.constraint));
444                        }
445                        Some(def) => {
446                            write!(buf, "#{}", a.pattern)?;
447                            buf.push_str(typ!(&a.constraint));
448                            if let Some(def) = def {
449                                write!(buf, " = {def}")?;
450                            }
451                        }
452                    }
453                    if vargs.is_some() || i < args.len() - 1 {
454                        write!(buf, ", ")?
455                    }
456                }
457                if let Some(typ) = vargs {
458                    write!(buf, "@args{}", typ!(typ))?;
459                }
460                write!(buf, "| ")?;
461                if let Some(t) = rtype {
462                    write!(buf, "-> {t} ")?
463                }
464                match body {
465                    Either::Right(builtin) => {
466                        writeln!(buf, "'{builtin}")
467                    }
468                    Either::Left(body) => match &body.kind {
469                        ExprKind::Do { exprs } => {
470                            pretty_print_exprs(indent, limit, buf, exprs, "{", "}", ";")
471                        }
472                        _ => body.kind.pretty_print(indent, limit, false, buf),
473                    },
474                }
475            }
476            ExprKind::Eq { lhs, rhs } => binop!("==", lhs, rhs),
477            ExprKind::Ne { lhs, rhs } => binop!("!=", lhs, rhs),
478            ExprKind::Lt { lhs, rhs } => binop!("<", lhs, rhs),
479            ExprKind::Gt { lhs, rhs } => binop!(">", lhs, rhs),
480            ExprKind::Lte { lhs, rhs } => binop!("<=", lhs, rhs),
481            ExprKind::Gte { lhs, rhs } => binop!(">=", lhs, rhs),
482            ExprKind::And { lhs, rhs } => binop!("&&", lhs, rhs),
483            ExprKind::Or { lhs, rhs } => binop!("||", lhs, rhs),
484            ExprKind::Add { lhs, rhs } => binop!("+", lhs, rhs),
485            ExprKind::Sub { lhs, rhs } => binop!("-", lhs, rhs),
486            ExprKind::Mul { lhs, rhs } => binop!("*", lhs, rhs),
487            ExprKind::Div { lhs, rhs } => binop!("/", lhs, rhs),
488            ExprKind::Mod { lhs, rhs } => binop!("%", lhs, rhs),
489            ExprKind::Sample { lhs, rhs } => binop!("~", lhs, rhs),
490            ExprKind::Not { expr } => {
491                try_single_line!(true);
492                match &expr.kind {
493                    ExprKind::Do { exprs } => {
494                        pretty_print_exprs(indent, limit, buf, exprs, "!{", "}", ";")
495                    }
496                    _ => {
497                        writeln!(buf, "!(")?;
498                        expr.kind.pretty_print(indent + 2, limit, true, buf)?;
499                        push_indent(indent, buf);
500                        writeln!(buf, ")")
501                    }
502                }
503            }
504            ExprKind::ByRef(e) => {
505                try_single_line!(true);
506                write!(buf, "&")?;
507                e.kind.pretty_print(indent + 2, limit, false, buf)
508            }
509            ExprKind::Deref(e) => match &e.kind {
510                ExprKind::Connect { .. } | ExprKind::Qop(_) => {
511                    try_single_line!(true);
512                    writeln!(buf, "*(")?;
513                    e.kind.pretty_print(indent + 2, limit, newline, buf)?;
514                    writeln!(buf, ")")
515                }
516                _ => {
517                    try_single_line!(true);
518                    write!(buf, "*")?;
519                    e.kind.pretty_print(indent + 2, limit, false, buf)
520                }
521            },
522            ExprKind::Select { arg, arms } => {
523                try_single_line!(true);
524                write!(buf, "select ")?;
525                arg.kind.pretty_print(indent, limit, false, buf)?;
526                kill_newline!(buf);
527                writeln!(buf, " {{")?;
528                for (i, (pat, expr)) in arms.iter().enumerate() {
529                    if let Some(tp) = &pat.type_predicate {
530                        write!(buf, "{tp} as ")?;
531                    }
532                    write!(buf, "{} ", pat.structure_predicate)?;
533                    if let Some(guard) = &pat.guard {
534                        write!(buf, "if ")?;
535                        guard.kind.pretty_print(indent + 2, limit, false, buf)?;
536                        kill_newline!(buf);
537                        write!(buf, " ")?;
538                    }
539                    write!(buf, "=> ")?;
540                    if let ExprKind::Do { exprs } = &expr.kind {
541                        let term = if i < arms.len() - 1 { "}," } else { "}" };
542                        pretty_print_exprs(
543                            indent + 2,
544                            limit,
545                            buf,
546                            exprs,
547                            "{",
548                            term,
549                            ";",
550                        )?;
551                    } else if i < arms.len() - 1 {
552                        expr.kind.pretty_print(indent + 2, limit, false, buf)?;
553                        kill_newline!(buf);
554                        writeln!(buf, ",")?
555                    } else {
556                        expr.kind.pretty_print(indent, limit, false, buf)?;
557                    }
558                }
559                push_indent(indent, buf);
560                writeln!(buf, "}}")
561            }
562        }
563    }
564}
565
566impl fmt::Display for ExprKind {
567    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
568        fn write_binop(
569            f: &mut fmt::Formatter,
570            op: &str,
571            lhs: &Expr,
572            rhs: &Expr,
573        ) -> fmt::Result {
574            write!(f, "(")?;
575            write!(f, "{lhs} {op} {rhs}")?;
576            write!(f, ")")
577        }
578        fn print_exprs(
579            f: &mut fmt::Formatter,
580            exprs: &[Expr],
581            open: &str,
582            close: &str,
583            sep: &str,
584        ) -> fmt::Result {
585            write!(f, "{open}")?;
586            for i in 0..exprs.len() {
587                write!(f, "{}", &exprs[i])?;
588                if i < exprs.len() - 1 {
589                    write!(f, "{sep}")?
590                }
591            }
592            write!(f, "{close}")
593        }
594        let mut tbuf = CompactString::new("");
595        macro_rules! typ {
596            ($typ:expr) => {{
597                match $typ {
598                    None => "",
599                    Some(typ) => {
600                        tbuf.clear();
601                        write!(tbuf, ": {typ}")?;
602                        tbuf.as_str()
603                    }
604                }
605            }};
606        }
607        let exp = |export| if export { "pub " } else { "" };
608        match self {
609            ExprKind::Constant(v @ Value::String(_)) => {
610                v.fmt_ext(f, &parser::GRAPHIX_ESC, true)
611            }
612            ExprKind::Constant(v) => v.fmt_ext(f, &VAL_ESC, true),
613            ExprKind::Bind(b) => {
614                let Bind { doc, pattern, typ, export, value } = &**b;
615                if let Some(doc) = doc {
616                    if doc == "" {
617                        writeln!(f, "///")?
618                    } else {
619                        for line in doc.lines() {
620                            writeln!(f, "///{line}")?
621                        }
622                    }
623                }
624                write!(f, "{}let {pattern}{} = {value}", exp(*export), typ!(typ))
625            }
626            ExprKind::StructWith { source, replace } => {
627                match &source.kind {
628                    ExprKind::Ref { .. } => write!(f, "{{ {source} with ")?,
629                    _ => write!(f, "{{ ({source}) with ")?,
630                }
631                for (i, (name, e)) in replace.iter().enumerate() {
632                    match &e.kind {
633                        ExprKind::Ref { name: n }
634                            if Path::dirname(&**n).is_none()
635                                && Path::basename(&**n) == Some(&**name) =>
636                        {
637                            write!(f, "{name}")?
638                        }
639                        _ => write!(f, "{name}: {e}")?,
640                    }
641                    if i < replace.len() - 1 {
642                        write!(f, ", ")?
643                    }
644                }
645                write!(f, " }}")
646            }
647            ExprKind::Connect { name, value, deref } => {
648                let deref = if *deref { "*" } else { "" };
649                write!(f, "{deref}{name} <- {value}")
650            }
651            ExprKind::Use { name } => {
652                write!(f, "use {name}")
653            }
654            ExprKind::Ref { name } => {
655                write!(f, "{name}")
656            }
657            ExprKind::StructRef { source, field } => match &source.kind {
658                ExprKind::Ref { .. } => {
659                    write!(f, "{source}.{field}")
660                }
661                source => write!(f, "({source}).{field}"),
662            },
663            ExprKind::TupleRef { source, field } => match &source.kind {
664                ExprKind::Ref { .. } => {
665                    write!(f, "{source}.{field}")
666                }
667                source => write!(f, "({source}).{field}"),
668            },
669            ExprKind::Module { name, export, value } => {
670                write!(f, "{}mod {name}", exp(*export))?;
671                match value {
672                    ModuleKind::Resolved(_) | ModuleKind::Unresolved => Ok(()),
673                    ModuleKind::Inline(exprs) => print_exprs(f, &**exprs, "{", "}", "; "),
674                    ModuleKind::Dynamic { sandbox, sig, source } => {
675                        write!(f, " dynamic {{ {sandbox};")?;
676                        write!(f, " {sig};")?;
677                        write!(f, " source {source} }}")
678                    }
679                }
680            }
681            ExprKind::TypeCast { expr, typ } => write!(f, "cast<{typ}>({expr})"),
682            ExprKind::TypeDef(td) => write!(f, "{td}"),
683            ExprKind::Do { exprs } => print_exprs(f, &**exprs, "{", "}", "; "),
684            ExprKind::Lambda(l) => {
685                let Lambda { args, vargs, rtype, constraints, body } = &**l;
686                for (i, (tvar, typ)) in constraints.iter().enumerate() {
687                    write!(f, "{tvar}: {typ}")?;
688                    if i < constraints.len() - 1 {
689                        write!(f, ", ")?;
690                    }
691                }
692                write!(f, "|")?;
693                for (i, a) in args.iter().enumerate() {
694                    match &a.labeled {
695                        None => {
696                            write!(f, "{}", a.pattern)?;
697                            write!(f, "{}", typ!(&a.constraint))?;
698                        }
699                        Some(def) => {
700                            write!(f, "#{}", a.pattern)?;
701                            write!(f, "{}", typ!(&a.constraint))?;
702                            if let Some(def) = def {
703                                write!(f, " = {def}")?;
704                            }
705                        }
706                    }
707                    if vargs.is_some() || i < args.len() - 1 {
708                        write!(f, ", ")?
709                    }
710                }
711                if let Some(typ) = vargs {
712                    write!(f, "@args{}", typ!(typ))?;
713                }
714                write!(f, "| ")?;
715                if let Some(t) = rtype {
716                    write!(f, "-> {t} ")?
717                }
718                match body {
719                    Either::Right(builtin) => write!(f, "'{builtin}"),
720                    Either::Left(body) => write!(f, "{body}"),
721                }
722            }
723            ExprKind::Array { args } => print_exprs(f, args, "[", "]", ", "),
724            ExprKind::Any { args } => {
725                write!(f, "any")?;
726                print_exprs(f, args, "(", ")", ", ")
727            }
728            ExprKind::Tuple { args } => print_exprs(f, args, "(", ")", ", "),
729            ExprKind::Variant { tag, args } if args.len() == 0 => {
730                write!(f, "`{tag}")
731            }
732            ExprKind::Variant { tag, args } => {
733                write!(f, "`{tag}")?;
734                print_exprs(f, args, "(", ")", ", ")
735            }
736            ExprKind::Struct { args } => {
737                write!(f, "{{ ")?;
738                for (i, (n, e)) in args.iter().enumerate() {
739                    match &e.kind {
740                        ExprKind::Ref { name }
741                            if Path::dirname(&**name).is_none()
742                                && Path::basename(&**name) == Some(&**n) =>
743                        {
744                            write!(f, "{n}")?
745                        }
746                        _ => write!(f, "{n}: {e}")?,
747                    }
748                    if i < args.len() - 1 {
749                        write!(f, ", ")?
750                    }
751                }
752                write!(f, " }}")
753            }
754            ExprKind::Qop(e) => write!(f, "{}?", e),
755            ExprKind::StringInterpolate { args } => {
756                write!(f, "\"")?;
757                for s in args.iter() {
758                    match &s.kind {
759                        ExprKind::Constant(Value::String(s)) if s.len() > 0 => {
760                            let es = parser::GRAPHIX_ESC.escape(&*s);
761                            write!(f, "{es}",)?;
762                        }
763                        s => {
764                            write!(f, "[{s}]")?;
765                        }
766                    }
767                }
768                write!(f, "\"")
769            }
770            ExprKind::ArrayRef { source, i } => match &source.kind {
771                ExprKind::Ref { .. } => {
772                    write!(f, "{}[{}]", source, i)
773                }
774                _ => write!(f, "({})[{}]", &source, &i),
775            },
776            ExprKind::ArraySlice { source, start, end } => {
777                let s = match start.as_ref() {
778                    None => "",
779                    Some(e) => &format_compact!("{e}"),
780                };
781                let e = match &end.as_ref() {
782                    None => "",
783                    Some(e) => &format_compact!("{e}"),
784                };
785                match &source.kind {
786                    ExprKind::Ref { .. } => {
787                        write!(f, "{}[{}..{}]", source, s, e)
788                    }
789                    _ => write!(f, "({})[{}..{}]", source, s, e),
790                }
791            }
792            ExprKind::Apply { args, function } => {
793                match &function.kind {
794                    ExprKind::Ref { name: _ } => write!(f, "{function}")?,
795                    function => write!(f, "({function})")?,
796                }
797                write!(f, "(")?;
798                for i in 0..args.len() {
799                    match &args[i].0 {
800                        None => write!(f, "{}", &args[i].1)?,
801                        Some(name) => match &args[i].1.kind {
802                            ExprKind::Ref { name: n }
803                                if Path::dirname(&n.0).is_none()
804                                    && Path::basename(&n.0) == Some(name.as_str()) =>
805                            {
806                                write!(f, "#{name}")?
807                            }
808                            _ => write!(f, "#{name}: {}", &args[i].1)?,
809                        },
810                    }
811                    if i < args.len() - 1 {
812                        write!(f, ", ")?
813                    }
814                }
815                write!(f, ")")
816            }
817            ExprKind::Select { arg, arms } => {
818                write!(f, "select {arg} {{")?;
819                for (i, (pat, rhs)) in arms.iter().enumerate() {
820                    if let Some(tp) = &pat.type_predicate {
821                        write!(f, "{tp} as ")?;
822                    }
823                    write!(f, "{} ", pat.structure_predicate)?;
824                    if let Some(guard) = &pat.guard {
825                        write!(f, "if {guard} ")?;
826                    }
827                    write!(f, "=> {rhs}")?;
828                    if i < arms.len() - 1 {
829                        write!(f, ", ")?
830                    }
831                }
832                write!(f, "}}")
833            }
834            ExprKind::Eq { lhs, rhs } => write_binop(f, "==", lhs, rhs),
835            ExprKind::Ne { lhs, rhs } => write_binop(f, "!=", lhs, rhs),
836            ExprKind::Gt { lhs, rhs } => write_binop(f, ">", lhs, rhs),
837            ExprKind::Lt { lhs, rhs } => write_binop(f, "<", lhs, rhs),
838            ExprKind::Gte { lhs, rhs } => write_binop(f, ">=", lhs, rhs),
839            ExprKind::Lte { lhs, rhs } => write_binop(f, "<=", lhs, rhs),
840            ExprKind::And { lhs, rhs } => write_binop(f, "&&", lhs, rhs),
841            ExprKind::Or { lhs, rhs } => write_binop(f, "||", lhs, rhs),
842            ExprKind::Add { lhs, rhs } => write_binop(f, "+", lhs, rhs),
843            ExprKind::Sub { lhs, rhs } => write_binop(f, "-", lhs, rhs),
844            ExprKind::Mul { lhs, rhs } => write_binop(f, "*", lhs, rhs),
845            ExprKind::Div { lhs, rhs } => write_binop(f, "/", lhs, rhs),
846            ExprKind::Mod { lhs, rhs } => write_binop(f, "%", lhs, rhs),
847            ExprKind::Sample { lhs, rhs } => write_binop(f, "~", lhs, rhs),
848            ExprKind::ByRef(e) => write!(f, "&{e}"),
849            ExprKind::Deref(e) => match &e.kind {
850                ExprKind::Qop(e) => write!(f, "*({e}?)"),
851                ExprKind::Connect { .. } => write!(f, "*({e})"),
852                _ => write!(f, "*{e}"),
853            },
854            ExprKind::Not { expr } => {
855                write!(f, "(!{expr})")
856            }
857        }
858    }
859}