xo-reader2: bugfix: need pattern match to fix operator precedence

This commit is contained in:
Roland Conybeare 2026-02-23 07:04:54 +11:00
commit 1fbbf70beb
3 changed files with 192 additions and 10 deletions

View file

@ -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<ASyntaxStateMachine,DProgressSsm>::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;

View file

@ -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;
}

View file

@ -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<Token> 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<AExpression,DApplyExpr>::from(result.result_expr());
REQUIRE(expr);
REQUIRE(expr->n_args() == 2);
auto fn = obj<AExpression,DConstant>::from(expr->fn());
REQUIRE(fn);
auto pm = obj<AGCObject,DPrimitive_gco_2_gco_gco>::from(fn->value());
REQUIRE(pm);
REQUIRE(pm->name() == "_sub");
auto lhs = obj<AExpression,DApplyExpr>::from(expr->arg(0));
REQUIRE(lhs);
auto lhs_lhs = obj<AExpression,DConstant>::from(lhs->arg(0));
REQUIRE(lhs_lhs);
auto lhs_lhs_i64 = obj<AGCObject,DInteger>::from(lhs_lhs->value());
REQUIRE(lhs_lhs_i64);
REQUIRE(lhs_lhs_i64->value() == 7);
auto lhs_rhs = obj<AExpression,DConstant>::from(lhs->arg(1));
REQUIRE(lhs_rhs);
auto lhs_rhs_i64 = obj<AGCObject,DInteger>::from(lhs_rhs->value());
REQUIRE(lhs_rhs_i64);
REQUIRE(lhs_rhs_i64->value() == 2);
auto rhs = obj<AExpression,DConstant>::from(expr->arg(1));
REQUIRE(rhs);
auto rhs_i64 = obj<AGCObject,DInteger>::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<Token> 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<AExpression,DApplyExpr>::from(result.result_expr());
REQUIRE(expr);
REQUIRE(expr->n_args() == 2);
auto fn = obj<AExpression,DConstant>::from(expr->fn());
REQUIRE(fn);
auto pm = obj<AGCObject,DPrimitive_gco_2_gco_gco>::from(fn->value());
REQUIRE(pm);
REQUIRE(pm->name() == "_sub");
auto lhs = obj<AExpression,DApplyExpr>::from(expr->arg(0));
REQUIRE(lhs);
auto lhs_lhs = obj<AExpression,DConstant>::from(lhs->arg(0));
REQUIRE(lhs_lhs);
auto lhs_lhs_i64 = obj<AGCObject,DInteger>::from(lhs_lhs->value());
REQUIRE(lhs_lhs_i64);
REQUIRE(lhs_lhs_i64->value() == 7);
auto lhs_rhs = obj<AExpression,DConstant>::from(lhs->arg(1));
REQUIRE(lhs_rhs);
auto lhs_rhs_i64 = obj<AGCObject,DInteger>::from(lhs_rhs->value());
REQUIRE(lhs_rhs_i64);
REQUIRE(lhs_rhs_i64->value() == 2);
auto rhs = obj<AExpression,DConstant>::from(expr->arg(1));
REQUIRE(rhs);
auto rhs_i64 = obj<AGCObject,DInteger>::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();