#include "eval-settings.hh" #include "lix/libexpr/nixexpr.hh" #include "lix/libexpr/parser/change_head.hh" #include "lix/libexpr/parser/grammar.hh" #include "lix/libexpr/parser/state.hh" #include "lix/libexpr/pos-idx.hh" #include "lix/libutil/finally.hh" #include "lix/libutil/users.hh" #include // flip this define when doing parser development to enable some g checks. #if 0 #include #define ANALYZE_GRAMMAR \ ([] { \ const std::size_t issues = tao::pegtl::analyze(); \ assert(issues == 0); \ })() #else #define ANALYZE_GRAMMAR ((void) 0) #endif namespace p = tao::pegtl; namespace nix::parser::v1 { namespace { template inline constexpr const char * error_message = nullptr; #define error_message_for(...) \ template<> inline constexpr auto error_message<__VA_ARGS__> error_message_for(p::one<'{'>) = "expecting '{'"; error_message_for(p::one<'}'>) = "expecting '}'"; error_message_for(p::one<'"'>) = "expecting '\"'"; error_message_for(p::one<';'>) = "expecting ';'"; error_message_for(p::one<')'>) = "expecting ')'"; error_message_for(p::one<']'>) = "expecting ']'"; error_message_for(p::one<':'>) = "expecting ':'"; error_message_for(p::string<'\'', '\''>) = "expecting \"''\""; error_message_for(p::any) = "expecting any character"; error_message_for(grammar::v1::eof) = "expecting end of file"; error_message_for(grammar::v1::seps) = "expecting separators"; error_message_for(grammar::v1::path::forbid_prefix_triple_slash) = "too many slashes in path"; error_message_for(grammar::v1::path::forbid_prefix_double_slash_no_interp) = "path has a trailing slash"; error_message_for(grammar::v1::expr) = "expecting expression"; error_message_for(grammar::v1::repl_root::expr_or_binding) = "expecting expression or a binding"; error_message_for(grammar::v1::expr::unary) = "expecting expression"; error_message_for(grammar::v1::binding::equal) = "expecting '='"; error_message_for(grammar::v1::expr::lambda::arg) = "expecting identifier"; error_message_for(grammar::v1::formals) = "expecting formals"; error_message_for(grammar::v1::attrpath) = "expecting attribute path"; error_message_for(grammar::v1::expr::select) = "expecting selection expression"; error_message_for(grammar::v1::t::kw_then) = "expecting 'then'"; error_message_for(grammar::v1::t::kw_else) = "expecting 'else'"; error_message_for(grammar::v1::t::kw_in) = "expecting 'in'"; error_message_for(grammar::v1::d::t::binding) = "expecting ';' to end binding"; error_message_for(grammar::v1::d::t::inherit) = "expecting ';' to end 'inherit' bindings"; error_message_for(grammar::v1::d::t::attrs) = "expecting '}' to end attribute set"; struct SyntaxErrors { template static constexpr auto message = error_message; template static constexpr bool raise_on_failure = false; }; template struct Control : p::must_if::control { template [[noreturn]] static void raise(const ParseInput & in, States &&... st) { if (in.empty()) { std::string expected; if constexpr (constexpr auto msg = error_message) expected = fmt(", %s", msg); throw p::parse_error("unexpected end of file" + expected, in); } p::must_if::control::raise(in, st...); } }; struct ExprState : grammar::v1:: operator_semantics>> { std::unique_ptr popExprOnly() { return std::move(popExpr().second); } template std::unique_ptr applyUnary(PosIdx pos, Args &&... args) { return std::make_unique(pos, popExprOnly(), std::forward(args)...); } template std::unique_ptr applyBinary(PosIdx pos) { auto right = popExprOnly(), left = popExprOnly(); return std::make_unique(pos, std::move(left), std::move(right)); } std::unique_ptr call(PosIdx pos, State & state, Symbol fn, bool flip = false) { std::vector> args(2); args[flip ? 0 : 1] = popExprOnly(); args[flip ? 1 : 0] = popExprOnly(); return std::make_unique(pos, state.mkInternalVar(pos, fn), std::move(args)); } std::unique_ptr pipe(PosIdx pos, State & state, bool flip = false) { if (!state.featureSettings.isEnabled(Xp::PipeOperator)) throw ParseError({ .msg = HintFmt("Pipe operator is disabled"), .pos = state.positions[pos] }); // Reverse the order compared to normal function application: arg |> fn std::unique_ptr fn, arg; if (flip) { fn = popExprOnly(); arg = popExprOnly(); } else { arg = popExprOnly(); fn = popExprOnly(); } std::vector> args{1}; args[0] = std::move(arg); return std::make_unique(pos, std::move(fn), std::move(args)); } std::unique_ptr order(PosIdx pos, bool less, State & state) { return call(pos, state, state.s.lessThan, !less); } std::unique_ptr concatStrings(PosIdx pos) { std::vector>> args(2); args[1] = popExpr(); args[0] = popExpr(); return std::make_unique(pos, false, std::move(args)); } std::unique_ptr negate(PosIdx pos, State & state) { std::vector> args(2); args[0] = std::make_unique(pos, 0); args[1] = popExprOnly(); return std::make_unique(pos, state.mkInternalVar(pos, state.s.sub), std::move(args)); } void applyOp(PosIdx pos, auto & op, State & state) { using Op = grammar::v1::op; auto not_ = [&] (auto e) { return std::make_unique(pos, std::move(e)); }; auto expr = (overloaded { [&] (Op::implies) { return applyBinary(pos); }, [&] (Op::or_) { return applyBinary(pos); }, [&] (Op::and_) { return applyBinary(pos); }, [&] (Op::equals) { return applyBinary(pos); }, [&] (Op::not_equals) { return applyBinary(pos); }, [&] (Op::less) { return order(pos, true, state); }, [&] (Op::greater_eq) { return not_(order(pos, true, state)); }, [&] (Op::greater) { return order(pos, false, state); }, [&] (Op::less_eq) { return not_(order(pos, false, state)); }, [&] (Op::update) { return applyBinary(pos); }, [&] (Op::not_) { return applyUnary(pos); }, [&] (Op::plus) { return concatStrings(pos); }, [&] (Op::minus) { return call(pos, state, state.s.sub); }, [&] (Op::mul) { return call(pos, state, state.s.mul); }, [&] (Op::div) { return call(pos, state, state.s.div); }, [&] (Op::concat) { return applyBinary(pos); }, [&] (has_attr & a) { return applyUnary(pos, std::move(a.path)); }, [&] (Op::unary_minus) { return negate(pos, state); }, [&] (Op::pipe_right) { return pipe(pos, state, true); }, [&] (Op::pipe_left) { return pipe(pos, state); }, })(op); pushExpr(pos, std::move(expr)); } // always_inline is needed, otherwise pushOp slows down considerably [[noreturn, gnu::always_inline]] static void badOperator(PosIdx pos, State & state) { throw ParseError({ .msg = HintFmt("syntax error, unexpected operator"), .pos = state.positions[pos] }); } template inline ExprT & emplaceExpr(PosIdx pos, Args && ... args) { auto p = std::make_unique(pos, std::forward(args)...); auto & result = *p; pushExpr(pos, std::move(p)); return result; } inline Expr & pushExpr(PosIdx pos, std::unique_ptr expr) { auto & result = *expr; exprs.emplace_back(pos, std::move(expr)); return result; } }; struct SubexprState { private: ExprState * up; public: explicit SubexprState(ExprState & up, auto &...) : up(&up) {} operator ExprState &() { return *up; } ExprState * operator->() { return up; } }; template struct BuildAST : grammar::v1::nothing {}; template<> struct BuildAST { static void apply(const auto & in, auto &, State & ps) { if (!ps.featureSettings.isEnabled(Dep::CRLineEndings)) ps.badLineEndingFound(ps.at(in), true); } }; struct SimpleLambdaState : SubexprState { using SubexprState::SubexprState; SimplePattern pattern; }; struct AttrsLambdaState : SubexprState { using SubexprState::SubexprState; AttrsPattern pattern; }; struct FormalsState : SubexprState { using SubexprState::SubexprState; std::vector formals; bool ellipsis = false; AttrsPattern::Formal formal {}; }; template<> struct BuildAST { static void apply(const auto & in, FormalsState & s, State & ps) { s.formal = { .pos = ps.at(in), .name = ps.symbols.create(in.string_view()), }; } }; template<> struct BuildAST { static void apply0(FormalsState & s, State &) { s.formals.emplace_back(std::move(s.formal)); } }; template<> struct BuildAST { static void apply0(FormalsState & s, State & ps) { s.formal.def = s->popExprOnly(); } }; template<> struct BuildAST { static void apply0(FormalsState & s, State &) { s.ellipsis = true; } }; template<> struct BuildAST : change_head { static void success0(FormalsState & f, AttrsLambdaState & s, State &) { s.pattern.formals = std::move(f.formals); s.pattern.ellipsis = f.ellipsis; } }; template<> struct BuildAST { static void apply(const auto & in, auto & s, State & ps) { s.pattern.name = ps.symbols.create(in.string_view()); } }; template<> struct BuildAST : change_head { static void success(const auto & in, SimpleLambdaState & l, ExprState & s, State & ps) { s.emplaceExpr(ps.at(in), std::make_unique(std::move(l.pattern)), l->popExprOnly()); } }; template<> struct BuildAST : change_head { static void success(const auto & in, AttrsLambdaState & l, ExprState & s, State & ps) { ps.validateLambdaAttrs(l.pattern, ps.at(in)); s.emplaceExpr(ps.at(in), std::make_unique(std::move(l.pattern)), l->popExprOnly()); } }; struct AttrState : SubexprState { using SubexprState::SubexprState; AttrPath attrs; template void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward(attr)); } }; template<> struct BuildAST { static void apply(const auto & in, auto & s, State & ps) { s.pushAttr(ps.symbols.create(in.string_view()), ps.at(in)); } }; template<> struct BuildAST { static void apply(const auto & in, auto & s, State & ps) { auto e = s->popExprOnly(); if (auto estr = dynamic_cast(e.get())) { s.pushAttr(ps.symbols.create(estr->str()), ps.at(in)); } else { s.pushAttr(std::move(e), ps.at(in)); } } }; template<> struct BuildAST : BuildAST {}; struct BindingState : AttrState { using AttrState::AttrState; std::unique_ptr value; }; struct BindingsState : SubexprState { explicit BindingsState(ExprState & up, ExprAttrs & attrs) : SubexprState(up), attrs(attrs) {} ExprAttrs & attrs; }; struct BindingsStateSet : BindingsState { ExprSet set = {}; BindingsStateSet(ExprState & up, State & ps, auto &...) : BindingsState(up, set) { } }; struct BindingsStateRecSet : BindingsState { ExprSet set = { PosIdx{}, true }; BindingsStateRecSet(ExprState & up, State & ps, auto &...) : BindingsState(up, set) { } }; struct BindingsStateLet : BindingsState { ExprLet let = {}; BindingsStateLet(ExprState & up, State & ps, auto &...) : BindingsState(up, let) { } }; struct InheritState : SubexprState { using SubexprState::SubexprState; std::vector attrs; std::unique_ptr from; PosIdx fromPos; template void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward(attr)); } }; template<> struct BuildAST { static void apply(const auto & in, InheritState & s, State & ps) { s.from = s->popExprOnly(); s.fromPos = ps.at(in); } }; template<> struct BuildAST : change_head { static void success0(InheritState & s, BindingsState & b, State & ps) { auto & attrs = b.attrs.attrs; // TODO this should not reuse generic attrpath rules. for (auto & i : s.attrs) { if (i.symbol) continue; if (auto estr = dynamic_cast(i.expr.get())) { i = AttrName(i.pos, ps.symbols.create(estr->str())); } else { throw ParseError({ .msg = HintFmt("dynamic attributes not allowed in inherit"), .pos = ps.positions[i.pos] }); } } if (s.from != nullptr) { if (!b.attrs.inheritFromExprs) b.attrs.inheritFromExprs = std::make_unique>>(); b.attrs.inheritFromExprs->push_back(std::move(s.from)); auto & fromExpr = b.attrs.inheritFromExprs->back(); for (auto & i : s.attrs) { if (attrs.find(i.symbol) != attrs.end()) ps.dupAttr(i.symbol, i.pos, attrs[i.symbol].pos); auto inheritFrom = std::make_unique( s.fromPos, b.attrs.inheritFromExprs->size() - 1, *fromExpr ); attrs.emplace( i.symbol, ExprAttrs::AttrDef( std::make_unique(i.pos, std::move(inheritFrom), i.pos, i.symbol), i.pos, ExprAttrs::AttrDef::Kind::InheritedFrom)); } } else { for (auto & i : s.attrs) { if (attrs.find(i.symbol) != attrs.end()) ps.dupAttr(i.symbol, i.pos, attrs[i.symbol].pos); attrs.emplace( i.symbol, ExprAttrs::AttrDef( std::make_unique(i.pos, i.symbol), i.pos, ExprAttrs::AttrDef::Kind::Inherited)); } } } }; template<> struct BuildAST { static void apply0(BindingState & s, State & ps) { s.value = s->popExprOnly(); } }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingState & b, BindingsState & s, State & ps) { ps.addAttr(&s.attrs, std::move(b.attrs), std::move(b.value), ps.at(in)); } }; struct BindingsStateRepl : ExprState { std::map> symbols; }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingState & b, BindingsStateRepl & s, State & ps) { auto path = std::move(b.attrs); AttrName name = std::move(path.front()); path.erase(path.begin()); if (name.expr) throw ParseError({ .msg = HintFmt("dynamic attributes not allowed in REPL"), .pos = ps.positions[ps.at(in)] }); Symbol symbol = name.symbol; if (auto iter = s.symbols.find(symbol); iter != s.symbols.end()) ps.dupAttr({symbol}, iter->second->getPos(), ps.at(in)); if (path.empty()) { // key = value s.symbols.emplace(symbol, std::move(b.value)); } else { // key.stuff = value auto attrs = std::make_unique(b.value->getPos()); ps.addAttr(&*attrs, std::move(path), std::move(b.value), ps.at(in)); s.symbols.emplace(symbol, std::move(attrs)); } } }; using ReplRootState = std::variant, ExprReplBindings>; template<> struct BuildAST : change_head { static void success0(BindingsStateRepl & b, ReplRootState & r, State &) { r = ExprReplBindings { std::move(b.symbols) }; } }; template<> struct BuildAST : change_head { static void success0(ExprState & inner, ReplRootState & outer, State & ps) { auto [_pos, expr] = inner.finish(ps); outer = std::move(expr); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { if (in.string_view() == "__curPos") s.emplaceExpr(ps.at(in)); else s.emplaceExpr(ps.at(in), ps.symbols.create(in.string_view())); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { int64_t v; if (std::from_chars(in.begin(), in.end(), v).ec != std::errc{}) { throw ParseError({ .msg = HintFmt("invalid integer '%1%'", in.string_view()), .pos = ps.positions[ps.at(in)], }); } s.emplaceExpr(ps.at(in), v); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { // copy the input into a temporary string so we can call stod. // can't use from_chars because libc++ (thus darwin) does not have it, // and floats are not performance-sensitive anyway. if they were you'd // be in much bigger trouble than this. // // we also get to do a locale-save dance because stod is locale-aware and // something (a plugin?) may have called setlocale or uselocale. static struct locale_hack { locale_t posix; locale_hack(): posix(newlocale(LC_ALL_MASK, "POSIX", 0)) { if (posix == 0) throw SysError("could not get POSIX locale"); } } locale; auto tmp = in.string(); double v = [&] { auto oldLocale = uselocale(locale.posix); Finally resetLocale([=] { uselocale(oldLocale); }); try { return std::stod(tmp); } catch (...) { throw ParseError({ .msg = HintFmt("invalid float '%1%'", in.string_view()), .pos = ps.positions[ps.at(in)], }); } }(); s.emplaceExpr(ps.at(in), NewValueAs::floating, v); } }; struct StringState : SubexprState { using SubexprState::SubexprState; std::string currentLiteral; PosIdx currentPos; std::vector>> parts; void append(PosIdx pos, std::string_view s) { if (currentLiteral.empty()) currentPos = pos; currentLiteral += s; } // FIXME this truncates strings on NUL for compat with the old parser. ideally // we should use the decomposition the g gives us instead of iterating over // the entire string again. void unescapeStr(std::string & str, State & ps) { char * s = str.data(); char * t = s; char c; while ((c = *s++)) { if (c == '\\') { c = *s++; if (c == 'n') *t = '\n'; else if (c == 'r') *t = '\r'; else if (c == 't') *t = '\t'; else *t = c; } else if (c == '\r') { /* Normalise CR and CR/LF into LF. */ *t = '\n'; if (*s == '\n') s++; /* cr/lf */ } else *t = c; t++; } if (!ps.featureSettings.isEnabled(Dep::NulBytes) && size_t(s - str.data() - 1) != str.size()) ps.nulFound(currentPos); str.resize(t - str.data()); } void endLiteral(State & ps) { if (!currentLiteral.empty()) { unescapeStr(currentLiteral, ps); parts.emplace_back(currentPos, std::make_unique(currentPos, std::move(currentLiteral))); } } std::unique_ptr finish(State & ps) { if (parts.empty()) { unescapeStr(currentLiteral, ps); return std::make_unique(currentPos, std::move(currentLiteral)); } else { endLiteral(ps); auto pos = parts[0].first; return std::make_unique(pos, true, std::move(parts)); } } }; template struct BuildAST> { static void apply(const auto & in, StringState & s, State & ps) { s.append(ps.at(in), in.string_view()); } }; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { if (!ps.featureSettings.isEnabled(Dep::CRLineEndings)) ps.badLineEndingFound(ps.at(in), false); else s.append(ps.at(in), in.string_view()); // FIXME compat with old parser } }; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { s.endLiteral(ps); s.parts.emplace_back(ps.at(in), s->popExprOnly()); } }; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { if (!ps.featureSettings.isEnabled(Dep::NulBytes) && *in.begin() == '\0') ps.nulFound(ps.at(in)); s.append(ps.at(in), "\\"); // FIXME compat with old parser s.append(ps.at(in), in.string_view()); } }; template<> struct BuildAST : change_head { static void success0(StringState & s, ExprState & e, State & ps) { e.pushExpr(noPos, s.finish(ps)); } }; struct IndStringState : SubexprState { using SubexprState::SubexprState; std::vector lines; }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { s.lines.push_back(IndStringLine { in.string_view(), ps.at(in) }); } }; template struct BuildAST> { static void apply(const auto & in, IndStringState & s, State & ps) { s.lines.back().parts.emplace_back(ps.at(in), in.string_view()); } }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { s.lines.back().parts.emplace_back(ps.at(in), s->popExprOnly()); } }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { switch (*in.begin()) { case 'n': s.lines.back().parts.emplace_back(ps.at(in), "\n"); break; case 'r': s.lines.back().parts.emplace_back(ps.at(in), "\r"); break; case 't': s.lines.back().parts.emplace_back(ps.at(in), "\t"); break; case 0: if (!ps.featureSettings.isEnabled(Dep::NulBytes)) { ps.nulFound(ps.at(in)); break; } KJ_FALLTHROUGH; default: s.lines.back().parts.emplace_back(ps.at(in), in.string_view()); break; } } }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { s.lines.back().hasContent = true; } }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { if (!ps.featureSettings.isEnabled(Dep::CRLineEndings)) ps.badLineEndingFound(ps.at(in), false); } }; template<> struct BuildAST { static void apply(const auto & in, IndStringState & s, State & ps) { if (!ps.featureSettings.isEnabled(Dep::NulBytes)) ps.nulFound(ps.at(in)); } }; template<> struct BuildAST : change_head { static void success(const auto & in, IndStringState & s, ExprState & e, State & ps) { e.pushExpr(noPos, ps.stripIndentation(ps.at(in), std::move(s.lines))); } }; template struct BuildAST> { static void apply(const auto & in, StringState & s, State & ps) { s.append(ps.at(in), in.string_view()); s.endLiteral(ps); } }; template<> struct BuildAST : BuildAST {}; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { Path path(absPath(in.string(), ps.basePath.canonical().abs())); /* add back in the trailing '/' to the first segment */ if (in.string_view().ends_with('/') && in.size() > 1) path += "/"; s.parts.emplace_back(ps.at(in), new ExprPath(ps.at(in), std::move(path))); } }; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { if (evalSettings.pureEval) throw Error("the path '%s' can not be resolved in pure mode", in.string_view()); Path path(getHome() + in.string_view().substr(1)); s.parts.emplace_back(ps.at(in), new ExprPath(ps.at(in), std::move(path))); } }; template<> struct BuildAST { static void apply(const auto & in, StringState & s, State & ps) { auto pos = ps.at(in); std::vector> args{2}; /* Overriding __nixPath, while being barely documented, is intended and supported: * https://github.com/NixOS/nix/commit/62a6eeb1f3da0a5954ad2da54c454eb7fc1c6e5d * (TODO: Provide a better and officially supported and documented mechanism for doing this) */ args[0] = std::make_unique(pos, ps.s.nixPath); args[1] = std::make_unique(pos, in.string()); s.parts.emplace_back( pos, std::make_unique( pos, /* The option for overriding this should be deprecated eventually, but for now it has to stay * until we can figure out how to design a better replacement. * https://git.lix.systems/lix-project/lix/issues/599 */ std::make_unique(pos, ps.s.findFile), std::move(args))); } }; template<> struct BuildAST : change_head { template static void check_slash(PosIdx end, StringState & s, State & ps) { auto e = dynamic_cast(s.parts.back().second.get()); if (!e || !e->str().ends_with('/')) { return; } if (s.parts.size() > 1 || e->str() != "/") { throw ParseError({ .msg = HintFmt("path has a trailing slash"), .pos = ps.positions[end], }); } } static void success(const auto & in, StringState & s, ExprState & e, State & ps) { s.endLiteral(ps); check_slash(ps.atEnd(in), s, ps); check_slash(ps.atEnd(in), s, ps); if (s.parts.size() == 1) { e.pushExpr(noPos, std::move(s.parts.back().second)); } else { e.emplaceExpr(ps.at(in), false, std::move(s.parts)); } } }; // strings and paths sare handled fully by the grammar-level rule for now template<> struct BuildAST : p::maybe_nothing {}; template<> struct BuildAST : p::maybe_nothing {}; template<> struct BuildAST : p::maybe_nothing {}; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { bool URLLiterals = ps.featureSettings.isEnabled(Dep::UrlLiterals); if (!URLLiterals) throw ParseError({ .msg = HintFmt("URL literals are deprecated, allow using them with %s", "--extra-deprecated-features url-literals"), .pos = ps.positions[ps.at(in)] }); s.emplaceExpr(ps.at(in), in.string()); } }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingsStateRecSet & b, ExprState & s, State & ps) { // Added 2024-09-18. Turn into an error at some point in the future. // See the documentation on deprecated features for more details. if (!ps.featureSettings.isEnabled(Dep::AncientLet)) //FIXME: why aren't there any tests for this? logWarning({ .msg = HintFmt( "%s is deprecated and will be removed in the future. Use %s to silence this warning.", "let {", "--extra-deprecated-features ancient-let" ), .pos = ps.positions[ps.at(in)] }); auto pos = ps.at(in); b.set.pos = pos; s.emplaceExpr(pos, std::make_unique(std::move(b.set)), pos, ps.s.body); } }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingsStateRecSet & b, ExprState & s, State & ps) { b.set.pos = ps.at(in); s.pushExpr(ps.at(in), std::make_unique(std::move(b.set))); } }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingsStateSet & b, ExprState & s, State & ps) { b.set.pos = ps.at(in); s.pushExpr(ps.at(in), std::make_unique(std::move(b.set))); } }; using ListState = std::vector>; template<> struct BuildAST : change_head { static void success(const auto & in, ListState & ls, ExprState & s, State & ps) { auto e = std::make_unique(ps.at(in)); e->elems = std::move(ls); s.pushExpr(ps.at(in), std::move(e)); } }; template<> struct BuildAST : change_head { static void success0(ExprState & e, ListState & s, State & ps) { s.emplace_back(e.finish(ps).second); } }; struct SelectState : SubexprState { using SubexprState::SubexprState; PosIdx pos; ExprSelect * e = nullptr; }; template<> struct BuildAST { static void apply(const auto & in, SelectState & s, State & ps) { s.pos = ps.at(in); } }; template<> struct BuildAST : change_head { static void success0(AttrState & a, SelectState & s, State &) { s.e = &s->emplaceExpr(s.pos, s->popExprOnly(), std::move(a.attrs), nullptr); } }; template<> struct BuildAST { static void apply0(SelectState & s, State &) { s.e->def = s->popExprOnly(); } }; template<> struct BuildAST { static void apply(const auto & in, SelectState & s, State & ps) { std::vector> args(1); args[0] = std::make_unique(ps.at(in), ps.s.or_); s->emplaceExpr(s.pos, s->popExprOnly(), std::move(args)); } }; template<> struct BuildAST : change_head { static void success0(const auto &...) {} }; struct AppState : SubexprState { using SubexprState::SubexprState; PosIdx pos; ExprCall * e = nullptr; }; template<> struct BuildAST { static void apply(const auto & in, AppState & s, State & ps) { s.pos = ps.at(in); } }; template<> struct BuildAST { static void apply(auto & in, AppState & s, State & ps) { auto arg = s->popExprOnly(), fn = s->popExprOnly(); if ((s.e = dynamic_cast(fn.get()))) { // TODO remove. // AST compat with old parser, semantics are the same. // this can happen on occasions such as `

` or `a or b or`, // neither of which are super worth optimizing. s.e->args.push_back(std::move(arg)); s->pushExpr(noPos, std::move(fn)); } else { std::vector> args{1}; args[0] = std::move(arg); s.e = &s->emplaceExpr(s.pos, std::move(fn), std::move(args)); } } }; template<> struct BuildAST { static void apply0(AppState & s, State & ps) { s.e->args.push_back(s->popExprOnly()); } }; template<> struct BuildAST : change_head { static void success0(const auto &...) {} }; template struct BuildAST> { static void apply(const auto & in, ExprState & s, State & ps) { s.pushOp(ps.at(in), Op{}, ps); } }; template<> struct BuildAST> : change_head { static void success(const auto & in, AttrState & a, ExprState & s, State & ps) { s.pushOp(ps.at(in), ExprState::has_attr{{}, std::move(a.attrs)}, ps); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { auto body = s.popExprOnly(), cond = s.popExprOnly(); s.emplaceExpr(ps.at(in), std::move(cond), std::move(body)); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { auto body = s.popExprOnly(), scope = s.popExprOnly(); s.emplaceExpr(ps.at(in), std::move(scope), std::move(body)); } }; template<> struct BuildAST : change_head { static void success(const auto & in, BindingsStateLet & b, ExprState & s, State & ps) { if (!b.let.dynamicAttrs.empty()) throw ParseError({ .msg = HintFmt("dynamic attributes not allowed in let"), .pos = ps.positions[ps.at(in)] }); b.let.body = b->popExprOnly(); b.let.pos = ps.at(in); s.pushExpr(ps.at(in), std::make_unique(std::move(b.let))); } }; template<> struct BuildAST { static void apply(const auto & in, ExprState & s, State & ps) { auto else_ = s.popExprOnly(), then = s.popExprOnly(), cond = s.popExprOnly(); s.emplaceExpr(ps.at(in), std::move(cond), std::move(then), std::move(else_)); } }; template<> struct BuildAST : change_head { static void success0(ExprState & inner, ExprState & outer, State & ps) { auto [pos, expr] = inner.finish(ps); outer.pushExpr(pos, std::move(expr)); } }; } }