From 1fbbf70beb32725de7efbf49ae9678c04960e747 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Feb 2026 07:04:54 +1100 Subject: [PATCH] xo-reader2: bugfix: need pattern match to fix operator precedence --- xo-reader2/src/reader2/DExpectExprSsm.cpp | 32 ++++- xo-reader2/src/reader2/DProgressSsm.cpp | 29 ++++- xo-reader2/utest/SchematikaParser.test.cpp | 141 +++++++++++++++++++++ 3 files changed, 192 insertions(+), 10 deletions(-) diff --git a/xo-reader2/src/reader2/DExpectExprSsm.cpp b/xo-reader2/src/reader2/DExpectExprSsm.cpp index 445ee76d..459e3176 100644 --- a/xo-reader2/src/reader2/DExpectExprSsm.cpp +++ b/xo-reader2/src/reader2/DExpectExprSsm.cpp @@ -5,6 +5,7 @@ #include "ExpectExprSsm.hpp" #include "ParserStateMachine.hpp" +#include "ParserStack.hpp" #include "SyntaxStateMachine.hpp" #include "ssm/ISyntaxStateMachine_DProgressSsm.hpp" #include "DSequenceSsm.hpp" @@ -28,15 +29,10 @@ #include "paren_xs.hpp" #include "sequence_xs.hpp" #include "progress_xs.hpp" -#include "xo/expression/Lambda.hpp" -#include "xo/expression/Constant.hpp" #include "xo/expression/pretty_expression.hpp" #endif namespace xo { -#ifdef NOT_YET - using xo::scm::Constant; -#endif using xo::scm::DFloat; using xo::mm::AGCObject; @@ -372,6 +368,32 @@ namespace xo { auto expr = DConstant::make(p_psm->expr_alloc(), i64o); + // Consider parser stack, with control here at b + // + // a * b - c + // ^ + // + // [1] ExpectExpr <-- this + // [0] ProgressSsm :lhs k(b) :op * :rhs _ + // + // if parent is ProgressSsm [0], need to deliver k(b) to it; + // Then let that ProgressSsm [0] establish whether next token + // is operator. + // + assert((void*)p_psm->stack()->top().data() == (void*)this); + + if (p_psm->stack()->parent()) { + auto parent_ssm = (obj::from + (p_psm->stack()->parent()->top())); + + if (parent_ssm) { + // parent is-a DProgressSsm instance + p_psm->pop_ssm(); + p_psm->on_parsed_expression(expr); + return; + } + } + // DProgressSsm responsible for resolving cases like // 1, // 1; diff --git a/xo-reader2/src/reader2/DProgressSsm.cpp b/xo-reader2/src/reader2/DProgressSsm.cpp index 9907869d..371c7e88 100644 --- a/xo-reader2/src/reader2/DProgressSsm.cpp +++ b/xo-reader2/src/reader2/DProgressSsm.cpp @@ -125,13 +125,13 @@ namespace xo { return optype::op_add; case tokentype::tk_minus: // [-] return optype::op_subtract; - case tokentype::tk_star: // [*] + case tokentype::tk_star: // [*] return optype::op_multiply; - case tokentype::tk_slash: // [/] + case tokentype::tk_slash: // [/] return optype::op_divide; - case tokentype::tk_cmpeq: // [==] + case tokentype::tk_cmpeq: // [==] return optype::op_equal; - case tokentype::tk_cmpne: + case tokentype::tk_cmpne: // [!=] return optype::op_not_equal; case tokentype::tk_leftangle: return optype::op_less; @@ -362,7 +362,12 @@ namespace xo { ParserStateMachine * p_psm) { if (op_type_ == optype::invalid) { - // tk is the operator this instance was waiting for + // 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); @@ -393,6 +398,7 @@ namespace xo { lhs2, op_type2, p_psm); + DExpectExprSsm::start(p_psm); return; } else { /* associate to the right @@ -512,6 +518,19 @@ namespace xo { 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; } diff --git a/xo-reader2/utest/SchematikaParser.test.cpp b/xo-reader2/utest/SchematikaParser.test.cpp index fa108af3..18d1c0ee 100644 --- a/xo-reader2/utest/SchematikaParser.test.cpp +++ b/xo-reader2/utest/SchematikaParser.test.cpp @@ -641,6 +641,147 @@ namespace xo { log && fixture.log_memory_layout(&log); } + TEST_CASE("SchematikaParser-interactive-arith3", "[reader2][SchematikaParser]") + { + const auto & testname = Catch::getResultCapture().getCurrentTestName(); + + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); + + ParserFixture fixture(testname, c_debug_flag); + auto & parser = *(fixture.parser_); + + parser.begin_interactive_session(); + + /** Walkthrough parsing input equivalent to: + * + * 7 * 2 - 3; + * + **/ + + std::vector tk_v{ + Token::i64_token("7"), + Token::star_token(), + Token::i64_token("2"), + Token::minus_token(), + Token::i64_token("3"), + Token::semicolon_token(), + }; + + INFO(testname); + + utest_tokenizer_loop(&parser, tk_v, c_debug_flag); + + const auto & result = parser.result(); + { + auto expr = obj::from(result.result_expr()); + REQUIRE(expr); + REQUIRE(expr->n_args() == 2); + + auto fn = obj::from(expr->fn()); + REQUIRE(fn); + + auto pm = obj::from(fn->value()); + REQUIRE(pm); + REQUIRE(pm->name() == "_sub"); + + auto lhs = obj::from(expr->arg(0)); + REQUIRE(lhs); + + auto lhs_lhs = obj::from(lhs->arg(0)); + REQUIRE(lhs_lhs); + auto lhs_lhs_i64 = obj::from(lhs_lhs->value()); + REQUIRE(lhs_lhs_i64); + REQUIRE(lhs_lhs_i64->value() == 7); + + auto lhs_rhs = obj::from(lhs->arg(1)); + REQUIRE(lhs_rhs); + auto lhs_rhs_i64 = obj::from(lhs_rhs->value()); + REQUIRE(lhs_rhs_i64); + REQUIRE(lhs_rhs_i64->value() == 2); + + auto rhs = obj::from(expr->arg(1)); + REQUIRE(rhs); + + auto rhs_i64 = obj::from(rhs->value()); + REQUIRE(rhs_i64); + REQUIRE(rhs_i64->value() == 3); + } + + log && fixture.log_memory_layout(&log); + } + + TEST_CASE("SchematikaParser-interactive-arith3-bad", "[reader2][SchematikaParser]") + { + const auto & testname = Catch::getResultCapture().getCurrentTestName(); + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); + + ParserFixture fixture(testname, c_debug_flag); + auto & parser = *(fixture.parser_); + + parser.begin_interactive_session(); + + /** Walkthrough parsing input equivalent to: + * + * 7 * 2 - 3 4; + * + **/ + + std::vector tk_v{ + Token::i64_token("7"), + Token::star_token(), + Token::i64_token("2"), + Token::minus_token(), + Token::i64_token("3"), + Token::i64_token("4"), + Token::semicolon_token(), + }; + + INFO(testname); + + utest_tokenizer_loop(&parser, tk_v, c_debug_flag); + + const auto & result = parser.result(); + { + auto expr = obj::from(result.result_expr()); + REQUIRE(expr); + REQUIRE(expr->n_args() == 2); + + auto fn = obj::from(expr->fn()); + REQUIRE(fn); + + auto pm = obj::from(fn->value()); + REQUIRE(pm); + REQUIRE(pm->name() == "_sub"); + + auto lhs = obj::from(expr->arg(0)); + REQUIRE(lhs); + + auto lhs_lhs = obj::from(lhs->arg(0)); + REQUIRE(lhs_lhs); + auto lhs_lhs_i64 = obj::from(lhs_lhs->value()); + REQUIRE(lhs_lhs_i64); + REQUIRE(lhs_lhs_i64->value() == 7); + + auto lhs_rhs = obj::from(lhs->arg(1)); + REQUIRE(lhs_rhs); + auto lhs_rhs_i64 = obj::from(lhs_rhs->value()); + REQUIRE(lhs_rhs_i64); + REQUIRE(lhs_rhs_i64->value() == 2); + + auto rhs = obj::from(expr->arg(1)); + REQUIRE(rhs); + + auto rhs_i64 = obj::from(rhs->value()); + REQUIRE(rhs_i64); + REQUIRE(rhs_i64->value() == 3); + } + + log && fixture.log_memory_layout(&log); + } + TEST_CASE("SchematikaParser-interactive-cmp", "[reader2][SchematikaParser]") { const auto & testname = Catch::getResultCapture().getCurrentTestName();