org.aya.prettier.ConcretePrettier Maven / Gradle / Ivy
// Copyright (c) 2020-2024 Tesla (Yinsen) Zhang.
// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file.
package org.aya.prettier;
import com.intellij.openapi.util.text.StringUtil;
import kala.collection.Seq;
import kala.collection.SeqLike;
import kala.collection.SeqView;
import kala.collection.immutable.ImmutableSeq;
import kala.collection.mutable.MutableList;
import kala.range.primitive.IntRange;
import org.aya.generic.Constants;
import org.aya.generic.Modifier;
import org.aya.generic.Nested;
import org.aya.generic.term.DTKind;
import org.aya.pretty.doc.Doc;
import org.aya.syntax.concrete.Expr;
import org.aya.syntax.concrete.Pattern;
import org.aya.syntax.concrete.stmt.*;
import org.aya.syntax.concrete.stmt.Stmt.Accessibility;
import org.aya.syntax.concrete.stmt.decl.*;
import org.aya.syntax.core.def.AnyDef;
import org.aya.syntax.ref.AnyDefVar;
import org.aya.syntax.ref.DefVar;
import org.aya.syntax.ref.LocalVar;
import org.aya.util.Arg;
import org.aya.util.binop.Assoc;
import org.aya.util.error.WithPos;
import org.aya.util.prettier.PrettierOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
import java.util.Objects;
import static org.aya.prettier.Tokens.*;
/**
* @author ice1000, kiva
*/
public class ConcretePrettier extends BasePrettier {
public ConcretePrettier(@NotNull PrettierOptions options) { super(options); }
public @NotNull Doc term(@NotNull Outer outer, @NotNull WithPos expr) {
return term(outer, expr.data());
}
@Override public @NotNull Doc term(@NotNull Outer outer, @NotNull Expr prexpr) {
return switch (prexpr) {
case Expr.Error error -> Doc.angled(error.description().toDoc(options));
case Expr.BinTuple (var lhs, var rhs) -> Doc.parened(Doc.commaList(term(Outer.Free, lhs), term(Outer.Free, rhs)));
case Expr.BinOpSeq binOpSeq -> {
var seq = binOpSeq.seq();
var first = seq.getFirst().term();
if (seq.sizeEquals(1)) yield term(outer, first);
yield visitCalls(null,
term(Outer.AppSpine, first),
seq.view().drop(1).map(x -> new Arg<>(x.arg().data(), x.explicit())),
outer,
optionImplicit()
);
}
case Expr.LitString expr -> Doc.plain('"' + StringUtil.unescapeStringCharacters(expr.string()) + '"');
case Expr.DepType expr -> {
var visitor = new Object() {
boolean paramRef = false;
boolean unresolved = false;
public void apply(@NotNull Expr e) {
switch (e) {
case Expr.Ref ref when ref.var() == expr.param().ref() -> paramRef = true;
case Expr.Unresolved _ -> unresolved = true;
default -> e.descent((_, t) -> {
apply(t);
return t;
});
}
}
};
visitor.apply(expr.last().data());
Doc doc;
var last = term(Outer.Codomain, expr.last().data());
if (expr.kind() == DTKind.Pi) {
if (!visitor.paramRef && !visitor.unresolved) {
doc = Doc.sep(justType(expr.param(), Outer.Domain), ARROW, last);
} else {
doc = Doc.sep(KW_PI, expr.param().toDoc(options), ARROW, last);
}
} else doc = Doc.sep(KW_SIGMA, expr.param().toDoc(options), SIGMA_RESULT, last);
// When outsider is neither a codomain nor non-expression, we need to add parentheses.
yield checkParen(outer, doc, Outer.Domain);
}
case Expr.App(var head, var args) -> {
Assoc assoc = null;
if (head.data() instanceof Expr.Ref ref && ref.var() instanceof AnyDefVar var)
assoc = AnyDef.fromVar(var).assoc();
yield visitConcreteCalls(assoc,
term(Outer.AppHead, head.data()),
args.view(), outer,
optionImplicit());
}
case Expr.Lambda expr -> {
var pair = Nested.destructNested(WithPos.dummy(expr));
var telescope = pair.component1();
var body = pair.component2().data();
var prelude = MutableList.of(LAMBDA);
var docTele = telescope.map(BasePrettier::varDoc);
prelude.appendAll(docTele);
if (!(body instanceof Expr.Hole hole && !hole.explicit())) {
prelude.append(FN_DEFINED_AS);
prelude.append(term(Outer.Free, body));
}
yield checkParen(outer, Doc.sep(prelude), Outer.BinOp);
}
case Expr.Hole expr -> {
if (!expr.explicit()) yield Doc.symbol(Constants.ANONYMOUS_PREFIX);
var filling = expr.filling();
if (filling == null) yield HOLE;
yield Doc.sep(HOLE_LEFT, term(Outer.Free, filling.data()), HOLE_RIGHT);
}
case Expr.Proj expr -> Doc.cat(term(Outer.ProjHead, expr.tup().data()), PROJ,
Doc.plain(expr.ix().fold(Objects::toString, QualifiedID::join)));
case Expr.Unresolved expr -> Doc.plain(expr.name().join());
case Expr.Ref expr -> {
var ref = expr.var();
yield ref instanceof DefVar, ?> defVar ? defVar(defVar) : varDoc(ref);
}
case Expr.LitInt expr -> Doc.plain(String.valueOf(expr.integer()));
case Expr.RawSort e -> Doc.styled(KEYWORD, e.kind().name());
case Expr.Sort expr -> {
var fn = Doc.styled(KEYWORD, expr.kind().name());
if (!expr.kind().hasLevel()) yield fn;
yield visitCalls(null, fn, (_, l) -> l.toDoc(options), outer,
SeqView.of(new Arg<>(_ -> Doc.plain(String.valueOf(expr.lift())), true)), true);
}
case Expr.Lift expr -> Doc.sep(Seq
.from(IntRange.closed(1, expr.lift()).iterator()).view()
.map(_ -> Doc.styled(KEYWORD, Doc.symbol("ulift")))
.appended(term(Outer.Lifted, expr.expr())));
case Expr.Idiom idiom -> Doc.wrap(
"(|", "|)",
Doc.join(Doc.symbol("|"), idiom.barredApps().view()
.map(app -> term(Outer.Free, app)))
);
case Expr.Do doExpr -> {
var doBlockDoc = doExpr.binds().map(this::visitDoBinding);
// Either not flat (single line) or full flat
yield Doc.stickySep(
// doExpr is atom! It cannot be `do\n{ ... }`
KW_DO,
Doc.flatAltBracedBlock(
Doc.commaList(doBlockDoc),
Doc.vcommaList(
doBlockDoc.map(x -> Doc.nest(2, x))
)
));
}
case Expr.Array arr -> arr.arrayBlock().fold(
left -> Doc.sep(
LIST_LEFT,
term(Outer.Free, left.generator()),
BAR,
Doc.commaList(left.binds().map(this::visitDoBinding)),
LIST_RIGHT
),
right -> Doc.sep(
LIST_LEFT,
Doc.commaList(right.exprList().view().map(e -> term(Outer.Free, e))), // Copied from Expr.Tup case
LIST_RIGHT
)
);
case Expr.Let let -> {
var letsAndBody = Nested.destructNested(WithPos.dummy(let));
var lets = letsAndBody.component1();
var body = letsAndBody.component2().data();
var oneLine = lets.sizeEquals(1);
var letSeq = oneLine
? visitLetBind(lets.getFirst())
: Doc.vcat(lets.view()
.map(this::visitLetBind)
// | f := g
.map(x -> Doc.sep(BAR, x)));
var docs = ImmutableSeq.of(KW_LET, letSeq, KW_IN);
// ```
// let a := b in
// ```
//
// or
//
// ```
// let
// | a := b
// | c := d
// in
// ```
var halfLet = oneLine ? Doc.sep(docs) : Doc.vcat(docs);
yield Doc.sep(halfLet, term(Outer.Free, body));
}
// let open Foo using (bar) in
// body
case Expr.LetOpen letOpen -> Doc.vcat(
Doc.sep(KW_LET, stmt(letOpen.openCmd()), KW_IN),
Doc.indent(2, term(Outer.Free, letOpen.body()))
);
case Expr.New neu -> Doc.sep(KW_NEW, term(Outer.Free, neu.classCall()));
case Expr.Match match -> {
var deltaDoc = match.discriminant().map(x -> term(Outer.Free, x));
var prefix = Doc.sep(KW_MATCH, Doc.commaList(deltaDoc));
var clauseDoc = visitClauses(match.clauses());
yield Doc.cblock(prefix, 2, clauseDoc);
}
};
}
public @NotNull Doc patterns(@NotNull ImmutableSeq patterns) {
return Doc.commaList(patterns.map(pattern -> pattern(pattern, true, Outer.Free)));
}
public @NotNull Doc pattern(@NotNull Arg pattern, Outer outer) {
return pattern(pattern.term(), pattern.explicit(), outer);
}
public @NotNull Doc pattern(@NotNull Pattern pattern, boolean licit, Outer outer) {
return switch (pattern) {
case Pattern.Tuple(var l, var r) ->
Doc.licit(licit, Doc.commaList(pattern(l.data(), true, Outer.Free), pattern(r.data(), true, Outer.Free)));
case Pattern.Absurd _ -> Doc.bracedUnless(PAT_ABSURD, licit);
case Pattern.Bind bind -> Doc.bracedUnless(linkDef(bind.bind()), licit);
case Pattern.CalmFace _ -> Doc.bracedUnless(Doc.plain(Constants.ANONYMOUS_PREFIX), licit);
case Pattern.Number number -> Doc.bracedUnless(Doc.plain(String.valueOf(number.number())), licit);
case Pattern.Con con -> {
var name = refVar(con.resolved().data());
var conDoc = con.params().isEmpty() ? name
: Doc.sep(name, visitMaybeConPatterns(con.params(), Outer.AppSpine, Doc.ALT_WS));
yield conDoc(outer, licit, conDoc, con.params().isEmpty());
}
case Pattern.QualifiedRef qref -> Doc.bracedUnless(Doc.plain(qref.qualifiedID().join()), licit);
case Pattern.BinOpSeq(var param) -> {
if (param.sizeEquals(1)) {
yield pattern(param.getFirst().map(WithPos::data), outer);
}
var ctorDoc = visitMaybeConPatterns(param.view(), Outer.AppSpine, Doc.ALT_WS);
yield conDoc(outer, licit, ctorDoc, param.sizeLessThanOrEquals(1));
}
case Pattern.List list -> Doc.sep(
LIST_LEFT,
Doc.commaList(list.elements().map(x -> pattern(x.data(), true, Outer.Free))),
LIST_RIGHT
);
case Pattern.As as -> {
var asBind = Seq.of(KW_AS, linkDef(as.as()));
if (outer == Outer.AppSpine) {
// {pattern as bind}
var inner = pattern(as.pattern().data(), true, Outer.Free);
yield Doc.licit(licit, Doc.sep(SeqView.of(inner).concat(asBind)));
} else {
var inner = pattern(as.pattern().data(), licit, Outer.Free);
yield Doc.sep(SeqView.of(inner).concat(asBind));
}
}
};
}
private Doc visitMaybeConPatterns(SeqLike>> patterns, Outer outer, @NotNull Doc delim) {
patterns = options.map.get(AyaPrettierOptions.Key.ShowImplicitPats) ? patterns : patterns.view().filter(Arg::explicit);
return Doc.join(delim, patterns.view().map(p -> pattern(p.map(WithPos::data), outer)));
}
public Doc matchy(@NotNull Pattern.Clause match) {
var doc = visitMaybeConPatterns(match.patterns, Outer.Free, Doc.plain(", "));
return match.expr.map(e -> Doc.sep(doc, FN_DEFINED_AS, term(Outer.Free, e))).getOrDefault(doc);
}
private Doc visitAccess(@NotNull Accessibility acc, @Nullable Accessibility theDefault) {
if (acc == theDefault) return Doc.empty();
else return Doc.styled(KEYWORD, acc.keyword);
}
public @NotNull Doc stmt(@NotNull Stmt prestmt) {
return switch (prestmt) {
case Decl decl -> decl(decl);
case Generalize variables -> Doc.sep(KW_VARIABLES, visitTele(variables.toExpr()));
case Command.Import cmd -> {
var prelude = MutableList.of(KW_IMPORT, Doc.symbol(cmd.path().toString()));
if (cmd.asName() != null) {
prelude.append(KW_AS);
prelude.append(Doc.plain(cmd.asName()));
}
yield Doc.sep(prelude);
}
case Command.Open cmd -> Doc.sepNonEmpty(
visitAccess(cmd.accessibility(), Accessibility.Private),
Doc.styled(KEYWORD, "open"),
Doc.plain(cmd.path().toString()),
Doc.styled(KEYWORD, cmd.useHide().strategy().name().toLowerCase(Locale.ROOT)),
Doc.parened(Doc.commaList(cmd.useHide().list().view()
.map(name -> name.asName().isEmpty()
|| name.id().component() == ModuleName.This && name.asName().get().equals(name.id().name())
? Doc.plain(name.id().name())
: Doc.sep(Doc.plain(name.id().join()), KW_AS, Doc.plain(name.asName().get())))))
);
case Command.Module mod -> Doc.vcat(
Doc.sep(visitAccess(mod.accessibility(), Accessibility.Public),
Doc.styled(KEYWORD, "module"),
Doc.plain(mod.name()),
Doc.symbol("{")),
Doc.nest(2, Doc.vcat(mod.contents().view().map(this::stmt))),
Doc.symbol("}")
);
};
}
public @NotNull Doc decl(@NotNull Decl predecl) {
return switch (predecl) {
case ClassDecl decl -> {
var prelude = MutableList.of(KW_CLASS);
prelude.append(defVar(decl.ref));
yield Doc.cat(Doc.sepNonEmpty(prelude),
Doc.emptyIf(decl.members.isEmpty(), () -> Doc.cat(Doc.line(), Doc.nest(2, Doc.vcat(
decl.members.view().map(this::decl))))),
visitBindBlock(decl.bindBlock())
);
}
case FnDecl decl -> {
var prelude = declPrelude(decl);
prelude.appendAll(Seq.from(decl.modifiers).view().map(this::visitModifier));
prelude.append(KW_DEF);
prelude.append(defVar(decl.ref));
prelude.append(visitTele(decl.telescope));
appendResult(prelude, decl.result);
yield Doc.cat(Doc.sepNonEmpty(prelude),
switch (decl.body) {
case FnBody.ExprBody(var expr) -> Doc.cat(Doc.spaced(FN_DEFINED_AS), term(Outer.Free, expr));
case FnBody.BlockBody(var clauses, var elims, var raw) -> Doc.vcat(
Doc.cat(Doc.spaced(KW_ELIM),
Doc.commaList(elims == null ? raw.map(name -> Doc.plain(name.data())) : elims.map(BasePrettier::varDoc))),
Doc.nest(2, visitClauses(clauses)));
},
visitBindBlock(decl.bindBlock())
);
}
case DataDecl decl -> {
var prelude = declPrelude(decl);
prelude.append(KW_DATA);
prelude.append(defVar(decl.ref));
prelude.append(visitTele(decl.telescope));
appendResult(prelude, decl.result);
yield Doc.cat(Doc.sepNonEmpty(prelude),
Doc.emptyIf(decl.body.isEmpty(), () -> Doc.cat(Doc.line(), Doc.nest(2, Doc.vcat(
decl.body.view().map(this::decl))))),
visitBindBlock(decl.bindBlock())
);
}
case ClassMember field -> {
var doc = MutableList.of(Doc.symbol("|"), defVar(field.ref), visitTele(field.telescope));
appendResult(doc, field.result);
/*field.body.ifDefined(body -> {
doc.append(Doc.symbol("=>"));
doc.append(term(Outer.Free, body));
});*/
yield Doc.sepNonEmpty(doc);
}
case DataCon con -> {
var ret = con.result == null ? Doc.empty() : Doc.sep(HAS_TYPE, term(Outer.Free, con.result));
var doc = Doc.sepNonEmpty(coe(con.coerce), defVar(con.ref), visitTele(con.telescope), ret);
if (con.patterns.isNotEmpty()) {
yield Doc.sep(BAR, Doc.commaList(con.patterns.map(pat ->
pattern(pat.map(WithPos::data), Outer.Free))), FN_DEFINED_AS, doc);
} else yield Doc.sep(BAR, doc);
}
case PrimDecl primDecl -> primDoc(primDecl.ref);
};
}
private @NotNull MutableList declPrelude(@NotNull Decl decl) {
return MutableList.of(visitAccess(decl.accessibility(), Accessibility.Public));
}
/**
* This function assumed that doBind.var()
is not {@link LocalVar#IGNORED}
*/
public @NotNull Doc visitDoBinding(@NotNull Expr.DoBind doBind) {
return doBind.var() == LocalVar.IGNORED
? term(Outer.Free, doBind.expr())
: Doc.sep(varDoc(doBind.var()), LARROW, term(Outer.Free, doBind.expr()));
}
private Doc visitClauses(@NotNull ImmutableSeq clauses) {
if (clauses.isEmpty()) return Doc.empty();
return Doc.vcat(clauses.view()
.map(this::matchy)
.map(doc -> Doc.sep(BAR, doc)));
}
private void appendResult(MutableList prelude, @Nullable WithPos result) {
if (result == null || result.data() instanceof Expr.Hole) return;
prelude.append(HAS_TYPE);
prelude.append(term(Outer.Free, result));
}
public Doc visitBindBlock(@NotNull BindBlock bindBlock) {
if (bindBlock == BindBlock.EMPTY) return Doc.empty();
var loosers = bindBlock.resolvedLoosers().get();
if (loosers == null) loosers = ImmutableSeq.empty();
var tighters = bindBlock.resolvedTighters().get();
if (tighters == null) tighters = ImmutableSeq.empty();
if (loosers.isEmpty() && tighters.isEmpty()) return Doc.empty();
if (loosers.isEmpty()) return Doc.cat(Doc.line(), Doc.hang(2, Doc.sep(
KW_TIGHTER,
Doc.commaList(tighters.view().map(BasePrettier::defVar)))));
else if (tighters.isEmpty()) return Doc.cat(Doc.line(), Doc.hang(2, Doc.sep(
KW_LOOSER,
Doc.commaList(loosers.view().map(BasePrettier::defVar)))));
return Doc.cat(Doc.line(), Doc.hang(2, Doc.cat(KW_BIND, Doc.braced(Doc.sep(
KW_TIGHTER, Doc.commaList(tighters.view().map(BasePrettier::defVar)),
KW_LOOSER, Doc.commaList(loosers.view().map(BasePrettier::defVar))
)))));
}
private @NotNull Doc visitLetBind(@NotNull Expr.LetBind letBind) {
// f : G := g
var prelude = MutableList.of(varDoc(letBind.bindName()));
if (letBind.telescope().isNotEmpty()) {
prelude.append(visitTele(letBind.telescope()));
}
appendResult(prelude, letBind.result());
prelude.append(DEFINED_AS);
prelude.append(term(Outer.Free, letBind.definedAs()));
return Doc.sep(prelude);
}
private @NotNull Doc visitModifier(@NotNull Modifier modifier) {
return Doc.styled(KEYWORD, modifier.keyword);
}
private @NotNull Doc visitConcreteCalls(
@Nullable Assoc assoc, @NotNull Doc fn,
@NotNull SeqView args,
@NotNull Outer outer, boolean showImplicits
) {
return visitCalls(assoc, fn, args.map(x -> new Arg<>(x.term().data(), x.explicit())), outer, showImplicits);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy