xo-interpreter2 stack: streamline op== impl + utests

This commit is contained in:
Roland Conybeare 2026-02-19 09:03:02 -08:00
commit 788363dd4c
20 changed files with 302 additions and 21 deletions

View file

@ -185,6 +185,14 @@ namespace xo {
if (expr_pr)
log && log(xtag("expr", expr_pr));
if (value_.value()) {
auto value_pr = const_cast<obj<AGCObject> *>(value_.value())->to_facet<APrintable>();
if (value_pr)
log && log(xtag("value", value_pr));
} else {
log && log("value not present or tk error");
}
auto stack_pr = stack_.to_facet<APrintable>();
if (stack_pr)
log && log(xtag("stack", stack_pr));

View file

@ -490,7 +490,7 @@ namespace xo {
log && vsm_fixture.log_memory_layout(&log);
}
TEST_CASE("VirtualSchematikaMachine-fact0", "[interpreter2][VSM]")
TEST_CASE("VirtualSchematikaMachine-if2", "[interpreter2][VSM]")
{
const auto & testname = Catch::getResultCapture().getCurrentTestName();
@ -504,6 +504,53 @@ namespace xo {
vsm.begin_interactive_session();
span_type input = span_type::from_cstr("def n = 4; if (n == 4) then n * 3 else n * 5;");
for (int i_expr = 0; i_expr < 2; ++i_expr) {
log && log(xtag("input", input));
VsmResultExt res
= vsm.read_eval_print(input, eof_flag);
REQUIRE(res.is_value());
REQUIRE(res.value());
log && log(xtag("res.tseq", res.value()->_typeseq()),
xtag("res.type", TypeRegistry::id2name(res.value()->_typeseq())));
if (i_expr == 0) {
auto x = obj<AGCObject,DUniqueString>::from(*res.value());
REQUIRE(x);
REQUIRE(strcmp(x->chars(), "n") == 0);
input = res.remaining_;
} else if (i_expr == 1) {
auto x = obj<AGCObject,DInteger>::from(*res.value());
REQUIRE(x);
REQUIRE(x->value() == 12);
REQUIRE(res.remaining_.size() == 1);
REQUIRE(*res.remaining_.lo() == '\n');
input = res.remaining_;
}
}
log && vsm_fixture.log_memory_layout(&log);
}
TEST_CASE("VirtualSchematikaMachine-fact0", "[interpreter2][VSM]")
{
const auto & testname = Catch::getResultCapture().getCurrentTestName();
bool c_debug_flag = false;
scope log(XO_DEBUG(c_debug_flag), xtag("test", testname));
VsmFixture vsm_fixture(testname, c_debug_flag);
auto & vsm = vsm_fixture.vsm_;
bool eof_flag = true;
vsm.begin_interactive_session();
span_type input = span_type::from_cstr("def fact = lambda (n) { if (n == 0) then 1 else n * fact(n - 1) }; fact(4);");
for (int i_expr = 0; i_expr < 2; ++i_expr) {

View file

@ -27,6 +27,9 @@ namespace xo {
DFloat * x, DInteger * y);
static obj<AGCObject> subtract(obj<ARuntimeContext> rcx,
DFloat * x, DInteger * y);
static obj<AGCObject> cmp_equal(obj<ARuntimeContext> rcx,
DFloat * x, DInteger * y);
};
class IntegerFloatOps {
@ -42,6 +45,9 @@ namespace xo {
DInteger * x, DFloat * y);
static obj<AGCObject> subtract(obj<ARuntimeContext> rcx,
DInteger * x, DFloat * y);
static obj<AGCObject> cmp_equal(obj<ARuntimeContext> rcx,
DInteger * x, DFloat * y);
};
}

View file

@ -26,6 +26,9 @@ namespace xo {
DFloat * x, DFloat * y);
static obj<AGCObject> subtract(obj<ARuntimeContext> rcx,
DFloat * x, DFloat * y);
static obj<AGCObject> cmp_equal(obj<ARuntimeContext> rcx,
DFloat * x, DFloat * y);
};
}

View file

@ -29,6 +29,9 @@ namespace xo {
static obj<AGCObject> subtract(obj<ARuntimeContext> rcx,
DInteger * x, DInteger * y);
static obj<AGCObject> cmp_equal(obj<ARuntimeContext> rcx,
DInteger * x, DInteger * y);
};
}

View file

@ -82,6 +82,11 @@ namespace xo {
obj<AGCObject> x,
obj<AGCObject> y);
/** compare two numeric values for equality **/
static obj<AGCObject> cmp_equal(obj<ARuntimeContext> rcx,
obj<AGCObject> x,
obj<AGCObject> y);
/** report memory use for owned arenas to @p visitor **/
void visit_pools(const MemorySizeVisitor & visitor);
@ -94,7 +99,8 @@ namespace xo {
void register_impl(typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl mul_fn,
typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl div_fn,
typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl add_fn,
typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl sub_fn) {
typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl sub_fn,
typename NumericOps<DRepr1, DRepr2>::BinaryOp_Impl cmpeq_fn) {
KeyType key(typeseq::id<DRepr1>().seqno(),
typeseq::id<DRepr2>().seqno());
@ -104,7 +110,8 @@ namespace xo {
= NumericOps<DRepr1, DRepr2>::make(mul_fn,
div_fn,
add_fn,
sub_fn);
sub_fn,
cmpeq_fn);
}
private:

View file

@ -25,13 +25,18 @@ namespace xo {
explicit AnonymizedNumericOps(BinaryOp multiply,
BinaryOp divide,
BinaryOp add,
BinaryOp subtract)
: multiply_{multiply}, divide_{divide}, add_{add}, subtract_{subtract} {}
BinaryOp subtract,
BinaryOp cmpeq)
: multiply_{multiply}, divide_{divide}, add_{add}, subtract_{subtract},
cmpeq_{cmpeq} {}
BinaryOp multiply_ = nullptr;
BinaryOp divide_ = nullptr;
BinaryOp add_ = nullptr;
BinaryOp subtract_ = nullptr;
/** compare numerics for equality **/
BinaryOp cmpeq_ = nullptr;
};
template <typename DRepr1, typename DRepr2>
@ -46,11 +51,13 @@ namespace xo {
static AnonymizedNumericOps make(BinaryOp_Impl multiply,
BinaryOp_Impl divide,
BinaryOp_Impl add,
BinaryOp_Impl subtract) {
BinaryOp_Impl subtract,
BinaryOp_Impl cmpeq) {
return AnonymizedNumericOps(reinterpret_cast<BinaryOp_Anon>(multiply),
reinterpret_cast<BinaryOp_Anon>(divide),
reinterpret_cast<BinaryOp_Anon>(add),
reinterpret_cast<BinaryOp_Anon>(subtract));
reinterpret_cast<BinaryOp_Anon>(subtract),
reinterpret_cast<BinaryOp_Anon>(cmpeq));
}
};

View file

@ -22,6 +22,9 @@ namespace xo {
static DPrimitive_gco_2_gco_gco s_add_gco_gco_pm;
/** polymorphic (in both arguments) subtract **/
static DPrimitive_gco_2_gco_gco s_sub_gco_gco_pm;
/** polymorphic (in both arguments) compare (==) **/
static DPrimitive_gco_2_gco_gco s_cmpeq_gco_gco_pm;
};
}
}

View file

@ -5,6 +5,7 @@
#include "FloatIntegerOps.hpp"
#include "float/INumeric_DFloat.hpp"
#include <xo/object2/Boolean.hpp>
namespace xo {
using xo::mm::AGCObject;
@ -41,6 +42,14 @@ namespace xo {
return DFloat::box<AGCObject>(rcx.allocator(), x->value() - y->value());
}
obj<AGCObject>
FloatIntegerOps::cmp_equal(obj<ARuntimeContext> rcx,
DFloat * x, DInteger * y)
{
return DBoolean::box<AGCObject>(rcx.allocator(),
x->value() == DFloat::value_type(y->value()));
}
// ----- Integer op Float -----
obj<AGCObject>
@ -71,6 +80,14 @@ namespace xo {
return DFloat::box<AGCObject>(rcx.allocator(), x->value() - y->value());
}
obj<AGCObject>
IntegerFloatOps::cmp_equal(obj<ARuntimeContext> rcx,
DInteger * x, DFloat * y)
{
return DFloat::box<AGCObject>(rcx.allocator(),
DFloat::value_type(x->value() == y->value()));
}
}
}

View file

@ -5,6 +5,7 @@
#include "FloatOps.hpp"
#include "float/INumeric_DFloat.hpp"
#include <xo/object2/Boolean.hpp>
namespace xo {
using xo::mm::AGCObject;
@ -39,6 +40,13 @@ namespace xo {
return DFloat::box<AGCObject>(rcx.allocator(), x->value() - y->value());
}
obj<AGCObject>
FloatOps::cmp_equal(obj<ARuntimeContext> rcx,
DFloat * x, DFloat * y)
{
return DBoolean::box<AGCObject>(rcx.allocator(), x->value() == y->value());
}
}
}

View file

@ -5,8 +5,10 @@
#include "IntegerOps.hpp"
#include "integer/INumeric_DInteger.hpp"
#include <xo/object2/Boolean.hpp>
namespace xo {
using xo::scm::DBoolean;
using xo::mm::AGCObject;
namespace scm {
@ -39,6 +41,13 @@ namespace xo {
return DInteger::box<AGCObject>(rcx.allocator(), x->value() - y->value());
}
obj<AGCObject>
IntegerOps::cmp_equal(obj<ARuntimeContext> rcx,
DInteger * x, DInteger * y)
{
return DBoolean::box<AGCObject>(rcx.allocator(), x->value() == y->value());
}
}
}

View file

@ -81,6 +81,22 @@ namespace xo {
return (*target_fn)(rcx, x.data(), y.data());
}
obj<AGCObject>
NumericDispatch::cmp_equal(obj<ARuntimeContext> rcx,
obj<AGCObject> x,
obj<AGCObject> y)
{
KeyType key(x._typeseq(), y._typeseq());
auto target_fn
= NumericDispatch::instance().dispatch_[key].cmpeq_;
if (!target_fn)
assert(false);
return (*target_fn)(rcx, x.data(), y.data());
}
} /*namespace scm*/
} /*namespace xo*/

View file

@ -27,6 +27,10 @@ namespace xo {
NumericPrimitives::s_sub_gco_gco_pm("_sub",
&NumericDispatch::subtract);
DPrimitive_gco_2_gco_gco
NumericPrimitives::s_cmpeq_gco_gco_pm("_cmpeq",
&NumericDispatch::cmp_equal);
} /*namespace scm*/
} /*namespace xo*/

View file

@ -38,25 +38,29 @@ namespace xo {
(&FloatOps::multiply,
&FloatOps::divide,
&FloatOps::add,
&FloatOps::subtract);
&FloatOps::subtract,
&FloatOps::cmp_equal);
NumericDispatch::instance().register_impl<DFloat, DInteger>
(&FloatIntegerOps::multiply,
&FloatIntegerOps::divide,
&FloatIntegerOps::add,
&FloatIntegerOps::subtract);
&FloatIntegerOps::subtract,
&FloatIntegerOps::cmp_equal);
NumericDispatch::instance().register_impl<DInteger, DFloat>
(&IntegerFloatOps::multiply,
&IntegerFloatOps::divide,
&IntegerFloatOps::add,
&IntegerFloatOps::subtract);
&IntegerFloatOps::subtract,
&IntegerFloatOps::cmp_equal);
NumericDispatch::instance().register_impl<DInteger, DInteger>
(&IntegerOps::multiply,
&IntegerOps::divide,
&IntegerOps::add,
&IntegerOps::subtract);
&IntegerOps::subtract,
&IntegerOps::cmp_equal);
log && log(xtag("ANumeric.tseq", typeseq::id<ANumeric>()));

View file

@ -24,7 +24,7 @@ namespace xo {
bool
DBoolean::pretty(const ppindentinfo & ppii) const
{
return ppdetail_atomic<bool>::print_pretty
return ppdetail_atomic<const char *>::print_pretty
(ppii,
(value_ ? "true" : "false"));
}

View file

@ -6,6 +6,7 @@
#include "RuntimeError.hpp"
namespace xo {
using xo::print::APrintable;
using xo::mm::AGCObject;
using xo::facet::typeseq;
@ -89,12 +90,14 @@ namespace xo {
bool
DRuntimeError::pretty(const ppindentinfo & ppii) const
{
return ppii.pps()->pretty_struct(ppii,
"DRuntimeError");
return ppii.pps()->pretty_struct
(ppii,
"DRuntimeError",
refrtag("src", obj<APrintable,DString>(src_function_)),
refrtag("err", obj<APrintable,DString>(error_descr_)));
}
} /*namespace scm*/
} /*namespace xo*/
/* end DRuntimeError.cpp */

View file

@ -29,13 +29,13 @@ namespace xo {
* so we can dispatch on vector, matrix, function types
**/
static DPrimitive_gco_2_gco_gco s_sub_gco_gco_pm;
#endif
/** polymorphic equality comparison
*
* TODO: this will want to move to x-numeric/
**/
static DPrimitive_gco_2_gco_gco s_equal_gco_gco_pm;
#endif
#ifdef NOT_YET
static Primitive_f64_1_f64 s_neg_f64_pm;

View file

@ -159,7 +159,6 @@ namespace xo {
xtag("y.tseq", y_tseq)));
return obj<AGCObject>();
}
#endif
obj<AGCObject>
equal_gco_gco(obj<ARuntimeContext> rcx,
@ -219,6 +218,7 @@ namespace xo {
xtag("y.tseq", y_tseq)));
return obj<AGCObject>();
}
#endif
#ifdef NOT_YET
double
@ -263,10 +263,10 @@ namespace xo {
DPrimitive_gco_2_gco_gco
Primitives::s_sub_gco_gco_pm("_sub", &sub_gco_gco);
#endif
DPrimitive_gco_2_gco_gco
Primitives::s_equal_gco_gco_pm("_equal", &equal_gco_gco);
#endif
#ifdef NOT_YET
Primitive_f64_1_f64

View file

@ -43,6 +43,7 @@ namespace xo {
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;
@ -1185,6 +1186,45 @@ namespace xo {
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,
DPrimitive_gco_2_gco_gco * p_gco_pm,
obj<AExpression> lhs,
obj<AExpression> rhs)
{
auto pm_obj = with_facet<AGCObject>::mkobj(p_gco_pm);
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);
}
}
obj<AExpression>
@ -1223,9 +1263,16 @@ namespace xo {
break;
case optype::op_equal:
return assemble_numeric_expr_aux
(p_psm->expr_alloc(),
TypeRef::prefix_type::from_chars("_cmpeq_gco"),
&NumericPrimitives::s_cmpeq_gco_gco_pm,
lhs_, rhs_);
#ifdef OBSOLETE
{
auto pm_obj = (with_facet<AGCObject>::mkobj
(&Primitives::s_equal_gco_gco_pm));
(&NumericPrimitives::s_cmpeq_gco_gco_pm));
auto fn_expr = (DConstant::make
(p_psm->expr_alloc(), pm_obj));
@ -1240,6 +1287,7 @@ namespace xo {
fn_expr, lhs_, rhs_);
}
break;
#endif
case optype::op_not_equal:
case optype::op_less:
@ -1250,6 +1298,13 @@ namespace xo {
break;
case optype::op_multiply:
return assemble_numeric_expr_aux
(p_psm->expr_alloc(),
TypeRef::prefix_type::from_chars("_mul_gco"),
&NumericPrimitives::s_mul_gco_gco_pm,
lhs_, rhs_);
#ifdef OBSOLETE
{
auto pm_obj = (with_facet<AGCObject>::mkobj
(&NumericPrimitives::s_mul_gco_gco_pm));
@ -1281,6 +1336,7 @@ namespace xo {
return DApplyExpr::make2(p_psm->expr_alloc(),
tref, fn_expr, lhs_, rhs_);
}
#endif
break;
case optype::op_divide:

View file

@ -11,6 +11,7 @@
#include <xo/reader2/init_reader2.hpp>
#include <xo/expression2/DefineExpr.hpp>
#include <xo/expression2/ApplyExpr.hpp>
#include <xo/expression2/IfElseExpr.hpp>
#include <xo/expression2/VarRef.hpp>
#include <xo/expression2/Constant.hpp>
#include <xo/procedure2/Primitive_gco_2_gco_gco.hpp>
@ -31,6 +32,7 @@ namespace xo {
using xo::scm::AExpression;
using xo::scm::DDefineExpr;
using xo::scm::DIfElseExpr;
using xo::scm::DApplyExpr;
using xo::scm::DVarRef;
using xo::scm::DConstant;
@ -680,7 +682,7 @@ namespace xo {
auto pm = obj<AGCObject,DPrimitive_gco_2_gco_gco>::from(fn->value());
REQUIRE(pm);
REQUIRE(pm->name() == "_equal");
REQUIRE(pm->name() == "_cmpeq");
auto lhs = obj<AExpression,DConstant>::from(expr->arg(0));
REQUIRE(lhs);
@ -700,6 +702,84 @@ namespace xo {
log && fixture.log_memory_layout(&log);
}
TEST_CASE("SchematikaParser-interactive-if1", "[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:
*
* def n = 4 ;
* ^ ^ ^ ^ ^
* 0 1 2 3 4
**/
std::vector<Token> tk_v{
/* [0] */ Token::def_token(),
/* [1] */ Token::symbol_token("n"),
/* [2] */ Token::singleassign_token(),
/* [3] */ Token::i64_token("4"),
/* [4] */ Token::semicolon_token()
};
utest_tokenizer_loop(&parser, tk_v, c_debug_flag);
}
{
const auto & result = parser.result();
auto expr = obj<AExpression,DDefineExpr>::from(result.result_expr());
REQUIRE(expr);
}
{
parser.reset_result();
/** Walkthrough parsing input equivalent to:
*
* if (n == 4) then 1 else n * 5 ;
* ^ ^^ ^ ^^ ^ ^ ^ ^ ^ ^ ^
* 0 1| 3 4| 6 7 8 9 a b c
* 2 5
**/
std::vector<Token> tk_v{
/* [0] */ Token::if_token(),
/* [1] */ Token::leftparen_token(),
/* [2] */ Token::symbol_token("n"),
/* [3] */ Token::cmpeq_token(),
/* [4] */ Token::i64_token("4"),
/* [5] */ Token::rightparen_token(),
/* [6] */ Token::then_token(),
/* [7] */ Token::i64_token("1"),
/* [8] */ Token::else_token(),
/* [9] */ Token::symbol_token("n"),
/* [a] */ Token::star_token(),
/* [b] */ Token::i64_token("5"),
/* [c] */ Token::semicolon_token()
};
utest_tokenizer_loop(&parser, tk_v, c_debug_flag);
}
const auto & result = parser.result();
{
auto expr = obj<AExpression,DIfElseExpr>::from(result.result_expr());
REQUIRE(expr);
}
log && fixture.log_memory_layout(&log);
}
TEST_CASE("SchematikaParser-interactive-lambda", "[reader2][SchematikaParser]")
{
const auto & testname = Catch::getResultCapture().getCurrentTestName();
@ -743,7 +823,7 @@ namespace xo {
log && fixture.log_memory_layout(&log);
}
TEST_CASE("SchematikaParser-interactive-if", "[reader2][SchematikaParser]")
TEST_CASE("SchematikaParser-interactive-if2", "[reader2][SchematikaParser]")
{
const auto & testname = Catch::getResultCapture().getCurrentTestName();