#include "lix/libexpr/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 <charconv>

// flip this define when doing parser development to enable some g checks.
#if 0
#include <tao/pegtl/contrib/analyze.hpp>
#define ANALYZE_GRAMMAR \
    ([] { \
        const std::size_t issues = tao::pegtl::analyze<grammar::v1::root>(); \
        assert(issues == 0); \
    })()
#else
#define ANALYZE_GRAMMAR ((void) 0)
#endif

namespace p = tao::pegtl;

namespace nix::parser::v1 {
namespace {

template<typename>
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<typename Rule>
    static constexpr auto message = error_message<Rule>;

    template<typename Rule>
    static constexpr bool raise_on_failure = false;
};

template<typename Rule>
struct Control : p::must_if<SyntaxErrors>::control<Rule>
{
    template<typename ParseInput, typename... States>
    [[noreturn]] static void raise(const ParseInput & in, States &&... st)
    {
        if (in.empty()) {
            std::string expected;
            if constexpr (constexpr auto msg = error_message<Rule>)
                expected = fmt(", %s", msg);
            throw p::parse_error("unexpected end of file" + expected, in);
        }
        p::must_if<SyntaxErrors>::control<Rule>::raise(in, st...);
    }
};

struct ExprState
    : grammar::v1::
          operator_semantics<ExprState, PosIdx, AttrPath, std::pair<PosIdx, std::unique_ptr<Expr>>>
{
    std::unique_ptr<Expr> popExprOnly() {
        return std::move(popExpr().second);
    }

    template<typename Op, typename... Args>
    std::unique_ptr<Expr> applyUnary(PosIdx pos, Args &&... args) {
        return std::make_unique<Op>(pos, popExprOnly(), std::forward<Args>(args)...);
    }

    template<typename Op>
    std::unique_ptr<Expr> applyBinary(PosIdx pos) {
        auto right = popExprOnly(), left = popExprOnly();
        return std::make_unique<Op>(pos, std::move(left), std::move(right));
    }

    std::unique_ptr<Expr> call(PosIdx pos, State & state, Symbol fn, bool flip = false)
    {
        std::vector<std::unique_ptr<Expr>> args(2);
        args[flip ? 0 : 1] = popExprOnly();
        args[flip ? 1 : 0] = popExprOnly();
        return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, fn), std::move(args));
    }

    std::unique_ptr<Expr> 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<Expr> fn, arg;
        if (flip) {
            fn = popExprOnly();
            arg = popExprOnly();
        } else {
            arg = popExprOnly();
            fn = popExprOnly();
        }
        std::vector<std::unique_ptr<Expr>> args{1};
        args[0] = std::move(arg);

        return std::make_unique<ExprCall>(pos, std::move(fn), std::move(args));
    }

    std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
    {
        return call(pos, state, state.symbols.sym___lessThan, !less);
    }

    std::unique_ptr<Expr> concatStrings(PosIdx pos)
    {
        std::vector<std::pair<PosIdx, std::unique_ptr<Expr>>> args(2);
        args[1] = popExpr();
        args[0] = popExpr();
        return std::make_unique<ExprConcatStrings>(pos, false, std::move(args));
    }

    std::unique_ptr<Expr> negate(PosIdx pos, State & state)
    {
        std::vector<std::unique_ptr<Expr>> args(2);
        args[0] = std::make_unique<ExprInt>(pos, 0);
        args[1] = popExprOnly();
        return std::make_unique<ExprCall>(
            pos, state.mkInternalVar(pos, state.symbols.sym___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<ExprOpNot>(pos, std::move(e));
        };

        auto expr = (overloaded{
            [&](Op::implies) { return applyBinary<ExprOpImpl>(pos); },
            [&](Op::or_) { return applyBinary<ExprOpOr>(pos); },
            [&](Op::and_) { return applyBinary<ExprOpAnd>(pos); },
            [&](Op::equals) { return applyBinary<ExprOpEq>(pos); },
            [&](Op::not_equals) { return applyBinary<ExprOpNEq>(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<ExprOpUpdate>(pos); },
            [&](Op::not_) { return applyUnary<ExprOpNot>(pos); },
            [&](Op::plus) { return concatStrings(pos); },
            [&](Op::minus) { return call(pos, state, state.symbols.sym___sub); },
            [&](Op::mul) { return call(pos, state, state.symbols.sym___mul); },
            [&](Op::div) { return call(pos, state, state.symbols.sym___div); },
            [&](Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
            [&](has_attr & a) { return applyUnary<ExprOpHasAttr>(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<typename ExprT, typename... Args>
    inline ExprT & emplaceExpr(PosIdx pos, Args && ... args)
    {
        auto p = std::make_unique<ExprT>(pos, std::forward<Args>(args)...);
        auto & result = *p;
        pushExpr(pos, std::move(p));
        return result;
    }

    inline Expr & pushExpr(PosIdx pos, std::unique_ptr<Expr> 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<typename Rule>
struct BuildAST : grammar::v1::nothing<Rule> {};

template<> struct BuildAST<grammar::v1::t::eol::deprecated_cr_crlf> {
    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<AttrsPattern::Formal> formals;
    bool ellipsis = false;

    AttrsPattern::Formal formal {};
};

template<> struct BuildAST<grammar::v1::formal::name> {
    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<grammar::v1::formal> {
    static void apply0(FormalsState & s, State &) {
        s.formals.emplace_back(std::move(s.formal));
    }
};

template<> struct BuildAST<grammar::v1::formal::default_value> {
    static void apply0(FormalsState & s, State & ps) {
        s.formal.def = s->popExprOnly();
    }
};

template<> struct BuildAST<grammar::v1::formals::ellipsis> {
    static void apply0(FormalsState & s, State &) {
        s.ellipsis = true;
    }
};

template<> struct BuildAST<grammar::v1::formals> : change_head<FormalsState> {
    static void success0(FormalsState & f, AttrsLambdaState & s, State &) {
        s.pattern.formals = std::move(f.formals);
        s.pattern.ellipsis = f.ellipsis;
    }
};

template<>
struct BuildAST<grammar::v1::expr::simple::noseps>
{
    static void apply(const auto & in, auto &, State & ps)
    {
        if (!ps.featureSettings.isEnabled(Dep::TokensNoWhitespace)) {
            ps.whitespaceBetweenTokensRequired(ps.at(in));
        }
    }
};

template<> struct BuildAST<grammar::v1::expr::lambda::arg> {
    static void apply(const auto & in, auto & s, State & ps) {
        s.pattern.name = ps.symbols.create(in.string_view());
    }
};

template<> struct BuildAST<grammar::v1::expr::lambda::pattern_simple> : change_head<SimpleLambdaState> {
    static void success(const auto & in, SimpleLambdaState & l, ExprState & s, State & ps) {
        s.emplaceExpr<ExprLambda>(ps.at(in), std::make_unique<SimplePattern>(std::move(l.pattern)), l->popExprOnly());
    }
};

template<> struct BuildAST<grammar::v1::expr::lambda::pattern_attrs> : change_head<AttrsLambdaState> {
    static void success(const auto & in, AttrsLambdaState & l, ExprState & s, State & ps) {
        ps.validateLambdaAttrs(l.pattern, ps.at(in));
        s.emplaceExpr<ExprLambda>(ps.at(in), std::make_unique<AttrsPattern>(std::move(l.pattern)), l->popExprOnly());
    }
};

struct AttrState : SubexprState {
    using SubexprState::SubexprState;

    AttrPath attrs;

    template <typename T>
    void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward<T>(attr)); }
};

template<> struct BuildAST<grammar::v1::attr::simple> {
    static void apply(const auto & in, auto & s, State & ps) {
        auto symbol = ps.symbols.create(in.string_view());
        if (!ps.featureSettings.isEnabled(Dep::OrAsIdentifier) && symbol == ps.symbols.sym_or) {
            ps.orIdentifierFound(ps.at(in));
        }
        s.pushAttr(symbol, ps.at(in));
    }
};

template<> struct BuildAST<grammar::v1::attr::string> {
    static void apply(const auto & in, auto & s, State & ps) {
        auto e = s->popExprOnly();
        if (auto estr = dynamic_cast<ExprString *>(e.get())) {
            s.pushAttr(ps.symbols.create(estr->str()), ps.at(in));
        } else {
            s.pushAttr(std::move(e), ps.at(in));
        }
    }
};

template<> struct BuildAST<grammar::v1::attr::expr> : BuildAST<grammar::v1::attr::string> {};

struct BindingState : AttrState {
    using AttrState::AttrState;

    std::unique_ptr<Expr> 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<AttrName> attrs;
    std::unique_ptr<Expr> from;
    PosIdx fromPos;

    template <typename T>
    void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(pos, std::forward<T>(attr)); }
};

template<> struct BuildAST<grammar::v1::inherit::from> {
    static void apply(const auto & in, InheritState & s, State & ps) {
        s.from = s->popExprOnly();
        s.fromPos = ps.at(in);
    }
};

template<> struct BuildAST<grammar::v1::inherit> : change_head<InheritState> {
    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<ExprString *>(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<std::list<std::unique_ptr<Expr>>>();
            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<ExprInheritFrom>(
                    s.fromPos,
                    b.attrs.inheritFromExprs->size() - 1,
                    *fromExpr
                );
                attrs.emplace(
                    i.symbol,
                    ExprAttrs::AttrDef(
                        std::make_unique<ExprSelect>(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<ExprVar>(i.pos, i.symbol),
                        i.pos,
                        ExprAttrs::AttrDef::Kind::Inherited));
            }
        }
    }
};

template<> struct BuildAST<grammar::v1::binding::value> {
    static void apply0(BindingState & s, State & ps) {
        s.value = s->popExprOnly();
    }
};

template<> struct BuildAST<grammar::v1::binding> : change_head<BindingState> {
    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<Symbol, std::unique_ptr<Expr>> symbols;
};

template<> struct BuildAST<grammar::v1::repl_binding> : change_head<BindingState> {
    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<ExprSet>(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<std::unique_ptr<Expr>, ExprReplBindings>;

template<> struct BuildAST<grammar::v1::repl_bindings> : change_head<BindingsStateRepl> {
    static void success0(BindingsStateRepl & b, ReplRootState & r, State &) {
        r = ExprReplBindings { std::move(b.symbols) };
    }
};

template<> struct BuildAST<grammar::v1::repl_root::expression> : change_head<ExprState> {
    static void success0(ExprState & inner, ReplRootState & outer, State & ps) {
        auto [_pos, expr] = inner.finish(ps);
        outer = std::move(expr);
    }
};

template<> struct BuildAST<grammar::v1::expr::id> {
    static void apply(const auto & in, ExprState & s, State & ps)
    {
        auto symbol_str = in.string_view();
        if (symbol_str == "__curPos") {
            s.emplaceExpr<ExprPos>(ps.at(in));
        } else if (symbol_str == "null" || symbol_str == "true" || symbol_str == "false") {
            // These should be literals really, but in Nix land they're mere identifiers with
            // builtins in scope We mark them specially to make sure shadowing them can be detected
            s.pushExpr(ps.at(in), ps.mkInternalVar(ps.at(in), ps.symbols.create(in.string_view())));
        } else {
            s.emplaceExpr<ExprVar>(ps.at(in), ps.symbols.create(in.string_view()));
        }
    }
};

template<> struct BuildAST<grammar::v1::expr::int_> {
    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<ExprInt>(ps.at(in), v);
    }
};

template<>
struct BuildAST<grammar::v1::t::integer::at_dot_id>
{
    static void apply(const auto & in, auto &, State & ps)
    {
        if (!ps.featureSettings.isEnabled(Dep::TokensNoWhitespace)) {
            ps.whitespaceBetweenTokensRequired(ps.at(in));
        }
    }
};

template<> struct BuildAST<grammar::v1::t::floating::no_leading_zero> {
    static void apply(const auto & in, ExprState & s, State & ps) {
        if (!ps.featureSettings.isEnabled(Dep::FloatingWithoutZero)) {
            logWarning(
                {.msg = HintFmt(
                     "Found floating point literal without leading zero. To fix this "
                     "warning, add a zero before the dot. Use %s to silence this warning",
                     "--extra-deprecated-feature floating-without-zero"
                 ),
                 .pos = ps.positions[ps.at(in)]}
            );
        }
    }
};

template<> struct BuildAST<grammar::v1::expr::float_> {
    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<ExprFloat>(ps.at(in), NewValueAs::floating, v);
    }
};

struct StringState : SubexprState {
    using SubexprState::SubexprState;

    std::string currentLiteral;
    PosIdx currentPos;
    std::vector<std::pair<nix::PosIdx, std::unique_ptr<Expr>>> 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<ExprString>(currentPos, std::move(currentLiteral)));
        }
    }

    std::unique_ptr<Expr> finish(State & ps)
    {
        if (parts.empty()) {
            unescapeStr(currentLiteral, ps);
            return std::make_unique<ExprString>(currentPos, std::move(currentLiteral));
        } else {
            endLiteral(ps);
            auto pos = parts[0].first;
            return std::make_unique<ExprConcatStrings>(pos, true, std::move(parts));
        }
    }
};

template<typename... Content> struct BuildAST<grammar::v1::string::literal<Content...>> {
    static void apply(const auto & in, StringState & s, State & ps) {
        s.append(ps.at(in), in.string_view());
    }
};

template<> struct BuildAST<grammar::v1::string::cr_crlf> {
    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<grammar::v1::string::interpolation> {
    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<grammar::v1::string::escape> {
    static void apply(const auto & in, StringState & s, State & ps) {
        char c = *in.begin();
        if (!ps.featureSettings.isEnabled(Dep::NulBytes) && c == '\0') {
            ps.nulFound(ps.at(in));
        }
        if (!ps.featureSettings.isEnabled(Dep::BrokenStringEscape) && c != '\\' && c != '$'
            && c != '"' && c != 'r' && c != 'n' && c != 't')
        {
            ps.badEscapeFound(ps.at(in), c, false);
        }
        s.append(ps.at(in), "\\"); // FIXME compat with old parser
        s.append(ps.at(in), in.string_view());
    }
};

template<> struct BuildAST<grammar::v1::string> : change_head<StringState> {
    static void success0(StringState & s, ExprState & e, State & ps) {
        e.pushExpr(noPos, s.finish(ps));
    }
};

struct IndStringState : SubexprState {
    using SubexprState::SubexprState;

    // If the first line (after the '') is empty it gets completely removed.
    // We track that in the grammar because no need to process it any further,
    // but we still require the information to know the actual number of lines
    // in the string.
    bool firstLineStripped = false;
    std::vector<IndStringLine> lines;
};

template<> struct BuildAST<grammar::v1::ind_string::strip_first_line> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        s.firstLineStripped = true;
    }
};

template<> struct BuildAST<grammar::v1::ind_string::line_start> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        s.lines.push_back(IndStringLine { in.string_view(), ps.at(in) });
    }
};

template<typename... Content>
struct BuildAST<grammar::v1::ind_string::literal<Content...>> {
    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<grammar::v1::ind_string::interpolation> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        s.lines.back().parts.emplace_back(ps.at(in), s->popExprOnly());
    }
};

template<> struct BuildAST<grammar::v1::ind_string::escape> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        auto c = *in.begin();
        switch (c) {
        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;
        // TODO merge with below
        // `''\'` must escape to itself even though one can just write `'` instead, because of
        // shit like
        // `''\'''${` to express the string `'${` (remember that `'''` escapes to `''`)
        case '\'':
            s.lines.back().parts.emplace_back(ps.at(in), "'");
            break;
        case 0:
            if (!ps.featureSettings.isEnabled(Dep::NulBytes)) {
                ps.nulFound(ps.at(in));
                break;
            }
            KJ_FALLTHROUGH;
        default:
            if (!ps.featureSettings.isEnabled(Dep::BrokenStringEscape)) {
                ps.badEscapeFound(ps.at(in), c, true);
            }

            s.lines.back().parts.emplace_back(ps.at(in), in.string_view());
            break;
        }
    }
};

template<> struct BuildAST<grammar::v1::ind_string::has_content> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        s.lines.back().hasContent = true;
    }
};

template<> struct BuildAST<grammar::v1::ind_string::cr> {
    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<grammar::v1::ind_string::nul> {
    static void apply(const auto & in, IndStringState & s, State & ps) {
        if (!ps.featureSettings.isEnabled(Dep::NulBytes))
            ps.nulFound(ps.at(in));
    }
};

template<> struct BuildAST<grammar::v1::ind_string> : change_head<IndStringState> {
    static void success(const auto & in, IndStringState & s, ExprState & e, State & ps) {
        if (!ps.featureSettings.isEnabled(Dep::BrokenStringIndent)) {
            /* Check for semantically incorrect code: Single-line string with indentation */
            if (s.lines.size() == 1 && !s.firstLineStripped && s.lines.front().indentation.size() > 0) {
                ps.badSingleLineIndStringFound(ps.at(in));
            }
            /* Check for semantically incorrect code: Multi-line string with text on the first line */
            if (s.lines.size() > 1 && !s.firstLineStripped) {
                ps.badFirstLineIndStringFound(ps.at(in));
            }
        }
        e.pushExpr(noPos, ps.stripIndentation(ps.at(in), std::move(s.lines)));
    }
};

template<typename... Content> struct BuildAST<grammar::v1::path::literal<Content...>> {
    static void apply(const auto & in, StringState & s, State & ps) {
        s.append(ps.at(in), in.string_view());
        s.endLiteral(ps);
    }
};

template<> struct BuildAST<grammar::v1::path::interpolation> : BuildAST<grammar::v1::string::interpolation> {};

template<> struct BuildAST<grammar::v1::path::anchor> {
    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<grammar::v1::path::home_anchor> {
    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<grammar::v1::path::searched_path> {
    static void apply(const auto & in, StringState & s, State & ps) {
        auto pos = ps.at(in);
        std::vector<std::unique_ptr<Expr>> 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<ExprVar>(pos, ps.symbols.sym___nixPath);
        args[1] = std::make_unique<ExprString>(pos, in.string());
        s.parts.emplace_back(
            pos,
            std::make_unique<ExprCall>(
                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<ExprVar>(pos, ps.symbols.sym___findFile),
                std::move(args)
            )
        );
    }
};

template<> struct BuildAST<grammar::v1::path> : change_head<StringState> {
    template<typename E>
    static void check_slash(PosIdx end, StringState & s, State & ps) {
        auto e = dynamic_cast<E *>(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<ExprPath>(ps.atEnd(in), s, ps);
        check_slash<ExprString>(ps.atEnd(in), s, ps);
        if (s.parts.size() == 1) {
            e.pushExpr(noPos, std::move(s.parts.back().second));
        } else {
            e.emplaceExpr<ExprConcatStrings>(ps.at(in), false, std::move(s.parts));
        }
    }
};

// strings and paths sare handled fully by the grammar-level rule for now
template<> struct BuildAST<grammar::v1::expr::string> : p::maybe_nothing {};
template<> struct BuildAST<grammar::v1::expr::ind_string> : p::maybe_nothing {};
template<> struct BuildAST<grammar::v1::expr::path> : p::maybe_nothing {};

template<> struct BuildAST<grammar::v1::expr::uri> {
    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<ExprString>(ps.at(in), in.string());
    }
};

template<> struct BuildAST<grammar::v1::expr::ancient_let> : change_head<BindingsStateRecSet> {
    static void success(const auto & in, BindingsStateRecSet & b, ExprState & s, State & ps) {
        // Added 2024-09-18 as a warning, turned into error 2026-01-29.
        // See the documentation on deprecated features for more details.
        if (!ps.featureSettings.isEnabled(Dep::AncientLet))
            //FIXME: why aren't there any tests for this?
            throw ParseError(
                {.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<ExprSelect>(pos, std::make_unique<ExprSet>(std::move(b.set)), pos, ps.symbols.sym_body);
    }
};

template<> struct BuildAST<grammar::v1::expr::rec_set> : change_head<BindingsStateRecSet> {
    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<ExprSet>(std::move(b.set)));
    }
};

template<> struct BuildAST<grammar::v1::expr::set> : change_head<BindingsStateSet> {
    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<ExprSet>(std::move(b.set)));
    }
};

using ListState = std::vector<std::unique_ptr<Expr>>;

template<> struct BuildAST<grammar::v1::expr::list> : change_head<ListState> {
    static void success(const auto & in, ListState & ls, ExprState & s, State & ps) {
        auto e = std::make_unique<ExprList>(ps.at(in));
        e->elems = std::move(ls);
        s.pushExpr(ps.at(in), std::move(e));
    }
};

template<> struct BuildAST<grammar::v1::expr::list::entry> : change_head<ExprState> {
    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<grammar::v1::expr::select::head> {
    static void apply(const auto & in, SelectState & s, State & ps) {
        s.pos = ps.at(in);
    }
};

template<> struct BuildAST<grammar::v1::expr::select::attr> : change_head<AttrState> {
    static void success0(AttrState & a, SelectState & s, State &) {
        s.e = &s->emplaceExpr<ExprSelect>(s.pos, s->popExprOnly(), std::move(a.attrs), nullptr);
    }
};

template<> struct BuildAST<grammar::v1::expr::select::attr_or> {
    static void apply0(SelectState & s, State &) {
        s.e->def = s->popExprOnly();
    }
};

template<> struct BuildAST<grammar::v1::expr::select::as_app_or> {
    static void apply(const auto & in, SelectState & s, State & ps) {
        if (!ps.featureSettings.isEnabled(Dep::OrAsIdentifier)) {
            ps.orArgumentFound(ps.at(in));
        }

        std::vector<std::unique_ptr<Expr>> args(1);
        args[0] = std::make_unique<ExprVar>(ps.at(in), ps.symbols.sym_or);
        s->emplaceExpr<ExprCall>(s.pos, s->popExprOnly(), std::move(args));
    }
};

template<> struct BuildAST<grammar::v1::expr::select> : change_head<SelectState> {
    static void success0(const auto &...) {}
};

struct AppState : SubexprState {
    using SubexprState::SubexprState;

    PosIdx pos;
    ExprCall * e = nullptr;
};

template<> struct BuildAST<grammar::v1::expr::app::select_or_fn> {
    static void apply(const auto & in, AppState & s, State & ps) {
        s.pos = ps.at(in);
    }
};

template<> struct BuildAST<grammar::v1::expr::app::first_arg> {
    static void apply(auto & in, AppState & s, State & ps) {
        auto arg = s->popExprOnly(), fn = s->popExprOnly();
        if ((s.e = dynamic_cast<ExprCall *>(fn.get()))) {
            // TODO remove.
            // AST compat with old parser, semantics are the same.
            // this can happen on occasions such as `<p> <p>` 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<std::unique_ptr<Expr>> args{1};
            args[0] = std::move(arg);
            s.e = &s->emplaceExpr<ExprCall>(s.pos, std::move(fn), std::move(args));
        }
    }
};

template<> struct BuildAST<grammar::v1::expr::app::another_arg> {
    static void apply0(AppState & s, State & ps) {
        s.e->args.push_back(s->popExprOnly());
    }
};

template<> struct BuildAST<grammar::v1::expr::app> : change_head<AppState> {
    static void success0(const auto &...) {}
};

template<typename Op> struct BuildAST<grammar::v1::expr::operator_<Op>> {
    static void apply(const auto & in, ExprState & s, State & ps) {
        s.pushOp(ps.at(in), Op{}, ps);
    }
};
template<> struct BuildAST<grammar::v1::expr::operator_<grammar::v1::op::has_attr>> : change_head<AttrState> {
    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<grammar::v1::expr::assert_> {
    static void apply(const auto & in, ExprState & s, State & ps) {
        auto body = s.popExprOnly(), cond = s.popExprOnly();
        s.emplaceExpr<ExprAssert>(ps.at(in), std::move(cond), std::move(body));
    }
};

template<> struct BuildAST<grammar::v1::expr::with> {
    static void apply(const auto & in, ExprState & s, State & ps) {
        auto body = s.popExprOnly(), scope = s.popExprOnly();
        s.emplaceExpr<ExprWith>(ps.at(in), std::move(scope), std::move(body));
    }
};

template<> struct BuildAST<grammar::v1::expr::let> : change_head<BindingsStateLet> {
    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<ExprLet>(std::move(b.let)));
    }
};

template<> struct BuildAST<grammar::v1::expr::if_> {
    static void apply(const auto & in, ExprState & s, State & ps) {
        auto else_ = s.popExprOnly(), then = s.popExprOnly(), cond = s.popExprOnly();
        s.emplaceExpr<ExprIf>(ps.at(in), std::move(cond), std::move(then), std::move(else_));
    }
};

template<> struct BuildAST<grammar::v1::expr> : change_head<ExprState> {
    static void success0(ExprState & inner, ExprState & outer, State & ps) {
        auto [pos, expr] = inner.finish(ps);
        outer.pushExpr(pos, std::move(expr));
    }
};

}
}
