/** @file DProgressSsm.cpp
 *
 *  @author Roland Conybeare, Jan 2026
 **/

#include "ProgressSsm.hpp"

#include "DExpectExprSsm.hpp"
#include "ssm/ISyntaxStateMachine_DExpectExprSsm.hpp"

#include "ApplySsm.hpp"
#include "ParenSsm.hpp"

#include <xo/numeric/NumericPrimitives.hpp>

#include <xo/expression2/DApplyExpr.hpp>
#include <xo/expression2/detail/IExpression_DApplyExpr.hpp>

#include <xo/expression2/DConstant.hpp>
#include <xo/expression2/detail/IExpression_DConstant.hpp>

#include <xo/procedure2/init_primitives.hpp>  // for xo::scm::Primitives
#include <xo/procedure2/detail/IGCObject_DPrimitive_gco_2_gco_gco.hpp>

#include <xo/alloc2/GCObject.hpp>
#include <xo/printable2/Printable.hpp>
#include <xo/facet/FacetRegistry.hpp>
#include <xo/reflectutil/typeseq.hpp>
#include <xo/indentlog/print/cond.hpp>

#ifdef NOT_YET
#include "expect_expr_xs.hpp"
#include "pretty_exprstatestack.hpp"
#include "xo/expression/AssignExpr.hpp"
#include "xo/expression/Apply.hpp"
#include "xo/expression/pretty_expression.hpp"
#endif

namespace xo {
#ifdef NOT_YET
    using xo::scm::Expression;
    using xo::scm::AssignExpr;
    using xo::scm::Variable;
    using xo::scm::Apply;
#endif
    using xo::mm::AAllocator;
    using xo::mm::AGCObject;
    using xo::print::APrintable;
    using xo::facet::FacetRegistry;
    using xo::facet::with_facet;
    using xo::reflect::typeseq;

    namespace scm {
        const char *
        optype_descr(optype x) {
            switch (x) {
            case optype::invalid:
                return "?optype";
            case optype::op_assign:
                return "op:=";
            case optype::op_less:
                return "op<";
            case optype::op_less_equal:
                return "op<=";
            case optype::op_equal:
                return "op==";
            case optype::op_not_equal:
                return "op!=";
            case optype::op_great:
                return "op>";
            case optype::op_great_equal:
                return "op>=";
            case optype::op_add:
                return "op+";
            case optype::op_subtract:
                return "op-";
            case optype::op_multiply:
                return "op*";
            case optype::op_divide:
                return "op/";
            case optype::n_optype:
                break;
            }
            return "???";
        }

        /** higher-precedence operators bind before lower-preference operators **/
        int
        precedence(optype x) {
            switch (x) {
            case optype::invalid:
            case optype::n_optype:
                return 0;

            case optype::op_assign:
                return 1;

            case optype::op_less:
            case optype::op_less_equal:
            case optype::op_equal:
            case optype::op_not_equal:
            case optype::op_great:
            case optype::op_great_equal:
                return 2;

            case optype::op_add:
            case optype::op_subtract:
                return 3;

            case optype::op_multiply:
            case optype::op_divide:
                return 4;
            }

            return 0;
        }

        namespace {
            optype
            tk2op(const tokentype & tktype) {
                switch (tktype) {
                case tokentype::tk_assign:
                    return optype::op_assign;
                case tokentype::tk_plus:         // [+]
                    return optype::op_add;
                case tokentype::tk_minus:        // [-]
                    return optype::op_subtract;
                case tokentype::tk_star:         // [*]
                    return optype::op_multiply;
                case tokentype::tk_slash:        // [/]
                    return optype::op_divide;
                case tokentype::tk_cmpeq:        // [==]
                    return optype::op_equal;
                case tokentype::tk_cmpne:        // [!=]
                    return optype::op_not_equal;
                case tokentype::tk_leftangle:    // [<]
                    return optype::op_less;
                case tokentype::tk_cmple:        // [<=]
                    return optype::op_less_equal;
                case tokentype::tk_rightangle:   // [>]
                    return optype::op_great;
                case tokentype::tk_cmpge:        // [>=]
                    return optype::op_great_equal;
                default:
                    assert(false);
                    return optype::invalid;
                }
                return optype::invalid;
            }
        }

        DProgressSsm *
        DProgressSsm::_make(DArena & mm,
                            obj<AExpression> lhs,
                            optype op)
        {
            void * mem = mm.alloc(typeseq::id<DProgressSsm>(),
                                  sizeof(DProgressSsm));

            return new (mem) DProgressSsm(lhs, op);
        }

        obj<ASyntaxStateMachine,DProgressSsm>
        DProgressSsm::make(DArena & mm,
                           obj<AExpression> lhs,
                           optype op)
        {
            return obj<ASyntaxStateMachine,DProgressSsm>(_make(mm, lhs, op));
        }

        void
        DProgressSsm::start(DArena & parser_mm,
                            obj<AExpression> lhs,
                            optype op,
                            ParserStateMachine * p_psm)
        {
            DArena::Checkpoint ckp = parser_mm.checkpoint();

            auto ssm = DProgressSsm::make(parser_mm, lhs, op);

            p_psm->push_ssm(ckp, ssm);
        }

        void
        DProgressSsm::start(DArena & parser_mm,
                            obj<AExpression> lhs,
                            ParserStateMachine * p_psm)
        {
            start(parser_mm, lhs, optype::invalid, p_psm);
        }


        void
        DProgressSsm::start(DArena & parser_mm,
                            ParserStateMachine * p_psm)
        {
            start(parser_mm, obj<AExpression>(), p_psm);
        }

        DProgressSsm::DProgressSsm(obj<AExpression> valex,
                                   optype op)
          : lhs_{valex},
            op_type_{op}
        {}

        syntaxstatetype
        DProgressSsm::ssm_type() const noexcept
        {
            return syntaxstatetype::progress;
        }

#ifdef NOT_YET
        bool
        progress_xs::admits_f64() const { return false; }
#endif

        std::string_view
        DProgressSsm::get_expect_str() const noexcept {
            if (!lhs_) {
                return "expr1|leftparen";
            } else if (op_type_ == optype::invalid) {
                return "oper|semicolon|leftparen|rightparen|rightbrace";
            } else {
                return "expr2|leftparen";
            }
        }

        void
        DProgressSsm::on_token(const Token & tk,
                               ParserStateMachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()), xtag("tk", tk));

            switch (tk.tk_type()) {
            case tokentype::tk_then:
            case tokentype::tk_else:
            case tokentype::tk_comma:
                this->on_completing_token(tk, p_psm);
                return;

            case tokentype::tk_symbol:
                this->on_symbol_token(tk, p_psm);
                return;

            case tokentype::tk_colon:
                this->on_colon_token(tk, p_psm);
                return;

            case tokentype::tk_singleassign:
                this->on_singleassign_token(tk, p_psm);
                return;

            case tokentype::tk_string:
                this->on_string_token(tk, p_psm);
                return;

            case tokentype::tk_f64:
                this->on_f64_token(tk, p_psm);
                return;

            case tokentype::tk_i64:
                this->on_i64_token(tk, p_psm);
                return;

            case tokentype::tk_bool:
                this->on_bool_token(tk, p_psm);
                return;

            case tokentype::tk_semicolon:
                this->on_semicolon_token(tk, p_psm);
                return;

            case tokentype::tk_rightbrace:
                this->on_rightbrace_token(tk, p_psm);
                return;

            case tokentype::tk_leftparen:
                this->on_leftparen_token(tk, p_psm);
                return;

            case tokentype::tk_rightparen:
                this->on_rightparen_token(tk, p_psm);
                return;

            // all the not-yet handled cases
            case tokentype::tk_invalid:
            case tokentype::tk_def:
            case tokentype::tk_deftype:
            case tokentype::tk_if:
            case tokentype::tk_quote:
            case tokentype::tk_leftbracket:
            case tokentype::tk_rightbracket:
            case tokentype::tk_leftbrace:
            case tokentype::tk_dot:
            case tokentype::tk_doublecolon:
            case tokentype::tk_assign:
            case tokentype::tk_yields:
                break;

            case tokentype::tk_star:
            case tokentype::tk_slash:
            case tokentype::tk_plus:
            case tokentype::tk_minus:
            case tokentype::tk_cmpeq:
            case tokentype::tk_cmpne:
            case tokentype::tk_leftangle:
            case tokentype::tk_rightangle:
            case tokentype::tk_cmple:
            case tokentype::tk_cmpge:
                this->on_operator_token(tk, p_psm);
                return;

            case tokentype::tk_nil:
            case tokentype::tk_type:
            case tokentype::tk_lambda:
                break;
            case tokentype::tk_let:
            case tokentype::tk_in:
            case tokentype::tk_end:
            case tokentype::N:
                break;
            }

            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_symbol_token(const Token & tk,
                                      ParserStateMachine * p_psm)
        {
            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_completing_token(const Token & tk,
                                          ParserStateMachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            obj<AExpression> expr = this->assemble_expr(p_psm);

            p_psm->pop_ssm();  // completes self
            p_psm->on_parsed_expression_with_token(expr, tk);
        }

        void
        DProgressSsm::on_colon_token(const Token & tk,
                                     ParserStateMachine * p_psm)
        {
            p_psm->illegal_input_on_token("DProgressSsm::on_colon_token",
                                          tk,
                                          this->get_expect_str());
        }

        void
        DProgressSsm::on_singleassign_token(const Token & tk,
                                            ParserStateMachine * p_psm)
        {
            p_psm->illegal_input_on_token("DProgressSsm::on_singleassign_token",
                                          tk,
                                          this->get_expect_str());
        }

        void
        DProgressSsm::on_operator_token(const Token & tk,
                                        ParserStateMachine * p_psm)
        {
            if (op_type_ == optype::invalid) {
                // tk is the operator this instance was waiting for.
                // But need to consider precedence.
                // possibly need to take lhs_ expression and rotate it into new lhs of
                // surrounding ProgressSsm


                this->op_type_ = tk2op(tk.tk_type());

                DExpectExprSsm::start(p_psm);
                return;
            } else if (rhs_) {
                optype op_type2 = tk2op(tk.tk_type());

                /* already have {lhs_, op_type_, rhs_},
                 * with incoming op_type2 operator decides whether to parse like
                 *    (lhs_, op_type_, rhs_) op_type2 ...
                 * or
                 *    (lhs_, op_type_, (rhs_ op_type2 ...))
                 */

                if (precedence(op_type_) >= precedence(op_type2)) {
                    /* associate to the left
                     *
                     * parse              like
                     *   a + b - ...        (a + b) - ...
                     *   a * b - ...        (a * b) - ...
                     */

                    auto lhs2 = this->assemble_expr(p_psm);

                    p_psm->pop_ssm();

                    DProgressSsm::start(p_psm->parser_alloc(),
                                        lhs2,
                                        op_type2,
                                        p_psm);
                    DExpectExprSsm::start(p_psm);
                    return;
                } else {
                    /* associate to the right
                     *
                     * parse             like
                     *  a + b * ...        (a + (b * ...))
                     */

                    obj<AExpression> lhs1 = lhs_;
                    optype op_type1 = op_type_;
                    obj<AExpression> rhs1 = rhs_;

                    assert(lhs1);
                    assert(op_type1 != optype::invalid);
                    assert(rhs1);

                    p_psm->pop_ssm();

                    /* (a + ..) */
                    DProgressSsm::start(p_psm->parser_alloc(),
                                        lhs1,
                                        op_type1,
                                        p_psm);
                    DExpectExprSsm::start(p_psm);
                    /* (b * ..) */
                    DProgressSsm::start(p_psm->parser_alloc(),
                                        rhs1,
                                        op_type2,
                                        p_psm);
                    DExpectExprSsm::start(p_psm);
                    return;
                }
            }

            p_psm->illegal_input_on_token("DProgressSsm::on_operator_token",
                                          tk,
                                          this->get_expect_str());
        }

        void
        DProgressSsm::on_string_token(const Token & tk,
                                      ParserStateMachine * p_psm)
        {
            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_f64_token(const Token & tk,
                                   ParserStateMachine * p_psm)
        {
            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_i64_token(const Token & tk,
                                   ParserStateMachine * p_psm)
        {
            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_bool_token(const Token & tk,
                                    ParserStateMachine * p_psm)
        {
            Super::on_token(tk, p_psm);
        }

        void
        DProgressSsm::on_semicolon_token(const Token & tk,
                                         ParserStateMachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            /* note: implementation should parallel .on_rightparen_token() */

            (void)tk;

            obj<AExpression> expr = this->assemble_expr(p_psm);

            {
                obj<APrintable> expr_pr
                    = FacetRegistry::instance().variant<APrintable,AExpression>(expr);
                assert(expr_pr);
                log && log(xtag("expr", expr_pr));
            }

            p_psm->pop_ssm();
            p_psm->on_parsed_expression_with_token(expr, tk);
        }

        void
        DProgressSsm::on_rightbrace_token(const Token & tk,
                                          ParserStateMachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            (void)tk;

            obj<AExpression> expr = this->assemble_expr(p_psm);

            if (expr) {
                obj<APrintable> expr_pr
                    = FacetRegistry::instance().try_variant<APrintable,AExpression>(expr);
                assert(expr_pr);
                log && log(xtag("expr", expr_pr));
            } else {
                // illegal token if assemble failed
                Super::on_token(tk, p_psm);
                return;
            }

            p_psm->pop_ssm();
            p_psm->on_parsed_expression_with_token(expr, tk);
        }

        void
        DProgressSsm::on_parsed_expression(obj<AExpression> expr,
                                           ParserStateMachine * p_psm)
        {
            const bool c_debug_flag = p_psm->debug_flag();

            scope log(XO_DEBUG(c_debug_flag));

            if (!lhs_) {
                log && log("accepting expr1");

                this->lhs_ = expr;

                return;
            }

            assert (op_type_ != optype::invalid);

            if (!rhs_) {
                this->rhs_ = expr;

                // need next token before we know whether this DProgressSsm
                // is complete.  Consider input like 7 + 2 * 3 vs 7 * 2 + 3
                //

                return;
            }

            Super::on_parsed_expression(expr, p_psm);
        }

        void
        DProgressSsm::on_parsed_expression_with_token(obj<AExpression> expr,
                                                      const Token & tk,
                                                      ParserStateMachine * p_psm)
        {
            const bool c_debug_flag = p_psm->debug_flag();

            scope log(XO_DEBUG(c_debug_flag),
                      xtag("expr", expr),
                      xtag("tk", tk));

#ifdef NOT_YET
            if (!lhs_) {
                log && log("DProgressSsm: accepting expr1");

                this->lhs_ = expr;

                // now we have to handle tk!

                return;
            }
#endif

            // here: have lhs_ expression

            if (op_type_ == optype::invalid) {
                // e.g. control here on input like
                //   x : = 4 4

                p_psm->illegal_parsed_expression
                    ("DProgressSsm::on_parsed_expression_with_token",
                     expr,
                     this->get_expect_str());

                return;
            }

            this->rhs_ = expr;

            obj<AExpression> expr2 = this->assemble_expr(p_psm);

            if (expr2) {
                p_psm->pop_ssm();
                p_psm->on_parsed_expression_with_token(expr2, tk);
            }
        }

#ifdef NOT_YET
        void
        progress_xs::apply_type_error(const char * self_name,
                                      optype op,
                                      bp<Expression> expr1,
                                      bp<Expression> expr2,
                                      parserstatemachine * p_psm) const
        {
            std::string errmsg = tostr("incompatible argument types T1,T2 to op",
                                       xtag("op", op),
                                       xtag("T1", expr1->valuetype()),
                                       xtag("T2", expr2->valuetype()));

            p_psm->on_error(self_name, std::move(errmsg));
        }

        void
        progress_xs::on_expr(bp<Expression> expr,
                             parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()), xtag("expr", expr));

            /* note: previous token probably an operator,
             *       handled from progress_xs::on_operator_token(),
             *       which pushes expect_expr_xs::expect_rhs_expression()
             */

            constexpr const char * c_self_name = "progress_xs::on_expr";
            const char * exp = get_expect_str();

            if (lhs_) {
                if (op_type_ == optype::invalid) {
                    /* two consecutive expression without an operator */
                    this->illegal_input_on_expr(c_self_name, expr, exp, p_psm);
                }

#ifdef NOT_QUITE
                assert(result.get());

                /* this expression complete.. */
                std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

                /* ..but more operators could follow, so don't commit yet */
                p_stack->push_exprstate(progress_xs::make(result));
#endif

                this->rhs_ = expr.promote();
            } else {
                /* control here on input like
                 *   add(1,2)...
                 *
                 * add(1,2) needs to be handled inside a progress_xs
                 * instance because may be followed by an operator:
                 *   add(1,2) + ...
                 */
                this->lhs_ = expr.promote();
            }
        }

        void
        progress_xs::on_expr_with_semicolon(bp<Expression> expr,
                                            parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            log && log(xtag("lhs", lhs_), xtag("op", op_type_), xtag("expr", expr));

            constexpr const char * c_self_name = "progress_xs::on_expr_with_semicolon";
            const char * exp = get_expect_str();

            if (op_type_ == optype::invalid) {
                this->illegal_input_on_expr(c_self_name, expr, exp, p_psm);
            }

            this->rhs_ = expr.promote();

            // FORBIDDEN, because .on_semicolon_token() destroys *this before returning
            //   this->on_semicolon_token(token_type::semicolon(), p_psm);
            // INSTEAD, spell out the body

            rp<Expression> expr2 = this->assemble_expr(p_psm);

            if (expr2) {
                std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

                p_psm->on_expr_with_semicolon(expr2);
            }
        }
#endif

#ifdef NOT_YET
        void
        progress_xs::on_comma_token(const token_type & tk,
                                    parserstatemachine * p_psm)
        {
            /* note: implementation parllels .on_semicolon_token(), .on_rightparen_token() */

            scope log(XO_DEBUG(p_psm->debug_flag()));

            constexpr const char * self_name = "progress::xs::on_comma_token";

            auto & xs_stack = p_psm->xs_stack_;

            /* stack may be something like
             *
             *   applyexpr
             *   expect_expr_xs
             *   progress_xs
             *                  <-- comma
             *
             * 1. comma completes expression-in-progress
             */

            /* comma confirms stack expression */
            rp<Expression> expr = this->assemble_expr(p_psm);

            std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

            if (xs_stack.empty()) {
                throw std::runtime_error(tostr(self_name,
                                               ": expected non-empty parsing state"));
            }

            log && log(xtag("stack", &xs_stack));

            p_psm->top_exprstate().on_expr(expr, p_psm);

            /* now deliver comma */
            p_psm->top_exprstate().on_comma_token(tk, p_psm);
        }

        void
        progress_xs::on_semicolon_token(const token_type & /*tk*/,
                                        parserstatemachine * p_psm)
        {
            /* note: implementation parallels .on_rightparen_token() */

            scope log(XO_DEBUG(p_psm->debug_flag()));

            rp<Expression> expr = this->assemble_expr(p_psm);

            log && log(xtag("assembled-expr", expr));

            std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

            p_psm->on_expr_with_semicolon(expr);

            /* control here on input like:
             *   (1.234;
             *
             * a. '(' sets up stack [lparen_0:expect_rhs_expression]
             *     (see exprstate::on_leftparen())
             * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress]
             *     (see exprstate::on_f64())
             * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression]
             *     deliver expresssion to expect_rhs_expression.on_expr_with_semicolon()
             *     (see exprstate::on_expr_with_semicolon())
             * d. expr_rhs_expression forwards expression to [lparen_0]
             * e. lparen_0 would advance to [lparen_1],  but rejects semicolon
             */
        }

        void
        progress_xs::on_assign_token(const token_type & tk,
                                     parserstatemachine * p_psm)
        {
            this->on_operator_token(tk, p_psm);
        }
#endif

        void
        DProgressSsm::on_leftparen_token(const Token & tk,
                                         ParserStateMachine * p_psm)
        {
            if (!lhs_) {
                // leftparen begins possible lhs expression
                DParenSsm::start(p_psm);

                p_psm->on_token(Token::leftparen_token());
                return;
            }

            if (op_type_ == optype::invalid) {
                // input:
                ///  <--- F1 --->
                //   (..........)(.. )..
                //   <------ A1 ----->
                //   <------- X1 ------>
                //
                //   F1: expression evaluating to a function,
                //       parsed as fn_expr
                //   A1: expression parsed as a function call (i.e. apply-expression)
                //   X1: operator expression starting with A1
                //
                // before:
                //   [0] ProgressSsm     responsible for input beginning with F1
                //         .lhs = fn_expr, .op_type empty, .rhs empty
                //
                // after:
                //   [0] ApplySsm        responsible for function call A1
                //         .fn_expr = fn_expr
                //   [1] ProgressSsm     responsible for operator expression X1
                //         .lhs empty, .op_type empty, .rhs empty
                //
                // Remarks:
                // 1. keep ProgressSsm on the stack in case input continues like:
                //      fn_expr(args..) + ..
                //    i.e. to allow for infix operator following apply
                //

                obj<AExpression> fn_expr(this->lhs_);

                this->lhs_ = obj<AExpression>();

                DApplySsm::start(fn_expr, p_psm);
                // + send leftparen to just-pushed apply
                p_psm->on_token(tk);

                return;
            }

            Super::on_token(tk, p_psm);
        }

#ifdef NOT_YET
        /* editor bait: on_lparen */
        void
        progress_xs::on_leftparen_token(const token_type & tk,
                                        parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            /* input like:
             *   'foo(' -> expect function call.  might continue 'foo(a,b,c)'
             *   'foo+(' -> expect parenthesized expression.  might continue 'foo+(bar/2)'
             */

            if (op_type_ == optype::invalid) {
                /* start function call */
                assert(rhs_.get() == nullptr);

                rp<Expression> fn_expr = lhs_;

                /* reset this progress_xs back to empty state;
                 * apply_xs will be responsible for lhs_.
                 */
                lhs_ = nullptr;

#ifdef OBSOLETE
                /* don't unwind! want to handle input like
                 *   f(x,y)+g(z)
                 */
                /* unwind this progress_xs + replace with function call */
                std::unique_ptr<exprstate> self = p_psm->pop_exprstate();
#endif

                apply_xs::start(fn_expr, p_psm);

                /* control will reenter progress_xs via .on_expr() */
                return;
            }

            const char * exp = get_expect_str();
            constexpr const char * c_self_name = "exprstate::on_leftparen";

            this->illegal_input_on_token(c_self_name, tk, exp, p_psm);
        }
#endif

        void
        DProgressSsm::on_rightparen_token(const Token & tk,
                                          ParserStateMachine * p_psm)
        {
            /* note: implementation parallels .on_semicolon_token() */

            scope log(XO_DEBUG(p_psm->debug_flag()));

            /* stack may be something like:
             *
             *   [0] DProgressSsm
             *   [1] DExpectExprSsm
             *   [2] DApplySsm
             *
             * where we want rightparen to resolve in [2] DApplySsm,
             * after triggering completion of [0] and [1]
             */
            auto expr = this->assemble_expr(p_psm);

            if (expr) {
                /* 1. popping self from parser stack.. */
                p_psm->pop_ssm();
                /* 2. report parsed subexpr to parent ssm, along with the triggering rightparen **/
                p_psm->on_parsed_expression_with_token(expr, tk);

                return;
            }

            Super::on_token(tk, p_psm);
        }

#ifdef NOT_YET
         void
         progress_xs::on_rightparen_token(const token_type & tk,
                                          parserstatemachine * p_psm)
         {
             /* note: implementation parallels .on_semicolon_token() */

             scope log(XO_DEBUG(p_psm->debug_flag()));

             constexpr const char * self_name = "progress_xs::on_rightparen";

             auto & xs_stack = p_psm->xs_stack_;

             /* stack may be something like:
              *
              *   lparen_0
              *   expect_expr_xs
              *   expr_progress
              *                   <-- rightparen
              *
              * 1. rightparen completes expression-in-progress
              * 2. rightparen must then match innermost waiting lparen_0
              */

             /* right paren confirms stack expression */
             rp<Expression> expr = this->assemble_expr(p_psm);

             log && log(xtag("expr", expr),
                        xtag("do", "pop self + send {expr, rparen} -> parent"));

             std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

             if (xs_stack.empty()) {
                 throw std::runtime_error(tostr(self_name,
                                                ": expected non-empty parsing stack"));
             }

             log && log(xtag("stack", &xs_stack));

             p_psm->top_exprstate().on_expr(expr, p_psm);

             /* now deliver rightparen */
             p_psm->top_exprstate().on_rightparen_token(tk, p_psm);
         }

        void
        progress_xs::on_else_token(const token_type & tk,
                                   parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            rp<Expression> expr = this->assemble_expr(p_psm);

            log && log(xtag("assembled-expr", expr));

            std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

            p_psm->on_expr(expr);
            p_psm->on_else_token(tk);

            /* control here on input like:
             *
             *   if a > b then c else..
             */
        }

        void
        progress_xs::on_rightbrace_token(const token_type & tk,
                                         parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            rp<Expression> expr = this->assemble_expr(p_psm);

            log && log(xtag("assembled-expr", expr));

            std::unique_ptr<exprstate> self = p_psm->pop_exprstate();

            p_psm->on_expr(expr);
            p_psm->on_rightbrace_token(tk);

            /* control here on input like:
             *
             *   { n * n }
             */
        }
#endif

#ifdef OBSOLETE
        //void progress_xs::on_operator_token(const token_type & tk, parserstatemachine * p_psm)

        void
        progress_xs::on_bool_token(const token_type & tk,
                                   parserstatemachine * p_psm)
        {
            scope log(XO_DEBUG(p_psm->debug_flag()));

            constexpr const char * c_self_name = "progress_xs::on_bool_token";
            const char * exp = get_expect_str();

            if (this->op_type_ == optype::invalid) {
                this->illegal_input_on_token(c_self_name, tk, exp, p_psm);
            } else {
                exprstate::on_bool_token(tk, p_psm);
            }
        }

        void
        progress_xs::on_i64_token(const token_type & tk,
                                  parserstatemachine * p_psm)
        {
            constexpr bool c_debug_flag = true;
            scope log(XO_DEBUG(c_debug_flag));

            constexpr const char * c_self_name = "progress_xs::on_i64_token";
            const char * exp = get_expect_str();

            if (this->op_type_ == optype::invalid) {
                this->illegal_input_on_token(c_self_name, tk, exp, p_psm);
            } else {
                exprstate::on_i64_token(tk, p_psm);
            }
        }
#endif

        bool
        DProgressSsm::pretty(const xo::print::ppindentinfo & ppii) const
        {
            scope log(XO_DEBUG(false));
            log && log(xtag("lhs_.tseq", lhs_._typeseq()));
            log && log(xtag("rhs_.tseq", rhs_._typeseq()));

            obj<APrintable> lhs
                = FacetRegistry::instance().try_variant<APrintable,AExpression>(lhs_);

            obj<APrintable> rhs
                = FacetRegistry::instance().try_variant<APrintable,AExpression>(rhs_);

            bool lhs_present = lhs;
            bool rhs_present = rhs;
            bool op_present = (op_type_ != optype::invalid);

            return ppii.pps()->pretty_struct
                       (ppii,
                        "DProgressSsm",
                        refrtag("lhs", lhs, lhs_present),
                        refrtag("op", op_type_, op_present),
                        refrtag("rhs", rhs, rhs_present),
                        refrtag("expect", this->get_expect_str())
                        );
        }

        namespace {
            // make_builtin_apply_pm2

            // helper function for making apply expression that invokes
            // a binary primitive.  Use for numeric operators:
            // {*, /, +, -, ==}
            //
            obj<AExpression>
            assemble_numeric_expr_aux(obj<AAllocator> expr_alloc,
                                      const TypeRef::prefix_type & prefix,
                                      obj<AGCObject> pm_obj,
                                      obj<AExpression> lhs,
                                      obj<AExpression> rhs)
            {
                auto fn_expr = DConstant::make(expr_alloc, pm_obj);

                /* note:
                 * 1. don't assume we know lhs_ / rhs_ value types yet.
                 *    perhaps have expression like
                 *      f(..) * g(..)
                 *    where f is the function that contains current ssm.
                 *
                 * 2. consequence: we need representation for
                 *    polymorphic multiply on unknown numeric arguments.
                 *
                 * 3. TypeRef::dwim(..) is a placeholder.
                 *    Plan to later provide abstract interpreter
                 *    (ie compiler pass :) to drive type inference/unification
                 *
                 * 4. Alternatively could supply type-annotation syntax
                 *    so human can assist inference; context here is we want
                 *    to automate the boring stuff
                 */

                TypeRef tref = TypeRef::dwim(prefix, nullptr);

                return DApplyExpr::make2(expr_alloc,
                                         tref, fn_expr, lhs, rhs);

            }

#ifdef OBSOLETE
            obj<AExpression>
            assemble_numeric_expr_aux(obj<AAllocator> expr_alloc,
                                      const TypeRef::prefix_type & prefix,
                                      DPrimitive_gco_2_gco_gco * p_gco_pm,
                                      obj<AExpression> lhs,
                                      obj<AExpression> rhs)
            {
                auto pm_obj  = with_facet<AGCObject>::mkobj(p_gco_pm);

                return assemble_numeric_expr_aux(expr_alloc,
                                                 prefix,
                                                 pm_obj,
                                                 lhs, rhs);
            }
#endif
        }

        obj<AExpression>
        DProgressSsm::assemble_expr(ParserStateMachine * p_psm)
        {
            /* need to defer building Apply incase expr followed by higher-precedence operator:
             * consider input like
             *   3.14 + 2.0 * ...
             */

            constexpr const char * c_self_name = "DProgressSsm::assemble_expr";

            if ((op_type_ != optype::invalid) && !rhs_) {
                std::string errmsg_string = tostr("expected expression on rhs of operator op",
                                                  xtag("lhs", lhs_),
                                                  xtag("op", op_type_));

                auto errmsg = DString::from_view(p_psm->expr_alloc(),
                                                 std::string_view(errmsg_string));

                p_psm->capture_error(c_self_name, errmsg);
                return obj<AExpression>();
            }

            /* consecutive expressions not legal, e.g:
             *   3.14 6.28
             * but expressions surrounding an infix operators is:
             *   3.14 / 6.28
             */
            switch (op_type_) {
            case optype::invalid:
                return this->lhs_;

            case optype::op_assign:
                assert(false);
                break;

            case optype::op_equal:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmpeq_gco"),
                     p_psm->cmpeq_pm(),
                     lhs_, rhs_);

            case optype::op_not_equal:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmpne_gco"),
                     p_psm->cmpne_pm(),
                     lhs_, rhs_);

            case optype::op_less:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmplt_gco"),
                     p_psm->cmplt_pm(),
                     lhs_, rhs_);

            case optype::op_less_equal:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmple_gco"),
                     p_psm->cmple_pm(),
                     lhs_, rhs_);

            case optype::op_great:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmpgt_gco"),
                     p_psm->cmpgt_pm(),
                     lhs_, rhs_);
                break;

            case optype::op_great_equal:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_cmpge_gco"),
                     p_psm->cmpge_pm(),
                     lhs_, rhs_);

            case optype::op_multiply:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_mul_gco"),
                     p_psm->multiply_pm(),
                     lhs_, rhs_);

            case optype::op_divide:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_div_gco"),
                     p_psm->divide_pm(),
                     lhs_, rhs_);

            case optype::op_add:
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_add_gco"),
                     p_psm->add_pm(),
                     lhs_, rhs_);

                break;

            case optype::op_subtract: /* editor bait: op_minus */
                return assemble_numeric_expr_aux
                    (p_psm->expr_alloc(),
                     TypeRef::prefix_type::from_chars("_sub_gco"),
                     p_psm->subtract_pm(),
                     lhs_, rhs_);

#ifdef NOT_YET
case optype::op_assign:
    {
        bp<Variable> lhs = Variable::from(this->lhs_);

        if (!lhs) {
            throw std::runtime_error
                      (tostr("progress_xs::assemble_expr",
                             " expect variable on lhs of assignment operator :=",
                             xtag("lhs", lhs_),
                             xtag("rhs", rhs_)));
        }

        return AssignExpr::make(lhs.promote(),
                                this->rhs_);
    }

#endif

            case optype::n_optype:
                /* unreachable */
                assert(false);
                break;
            }

            return obj<AExpression>();
        }

        void
        DProgressSsm::visit_gco_children(VisitReason reason,
                                         obj<AGCObjectVisitor> gc) noexcept
        {
            gc.visit_poly_child(reason, &lhs_);
            gc.visit_poly_child(reason, &rhs_);
        }

    } /*namespace scm*/
} /*namespace xo*/

/* end DProgressSsm.cpp */
