From 4cd4328f071954e6bd247cc8cd48cab1038dee07 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 5 Feb 2026 10:44:11 -0500 Subject: [PATCH] xo-interpreter2 stack: work on variable references [WIP] --- include/xo/reader2/ExpectExprSsm.hpp | 12 ++ include/xo/reader2/ParserStateMachine.hpp | 8 + src/reader2/DExpectExprSsm.cpp | 41 +++-- src/reader2/DIfElseSsm.cpp | 3 +- src/reader2/ParserStateMachine.cpp | 57 +++++++ utest/SchematikaParser.test.cpp | 174 +++++++++++++++++++++- 6 files changed, 274 insertions(+), 21 deletions(-) create mode 100644 include/xo/reader2/ExpectExprSsm.hpp diff --git a/include/xo/reader2/ExpectExprSsm.hpp b/include/xo/reader2/ExpectExprSsm.hpp new file mode 100644 index 00000000..d46ef489 --- /dev/null +++ b/include/xo/reader2/ExpectExprSsm.hpp @@ -0,0 +1,12 @@ +/** @file ExpectExprSsm.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include "DExpectExprSsm.hpp" +#include "ssm/ISyntaxStateMachine_DExpectExprSsm.hpp" +#include "ssm/IPrintable_DExpectExprSsm.hpp" + +/* end ExpectExprSsm.hpp */ diff --git a/include/xo/reader2/ParserStateMachine.hpp b/include/xo/reader2/ParserStateMachine.hpp index 3fae8903..2bee6a96 100644 --- a/include/xo/reader2/ParserStateMachine.hpp +++ b/include/xo/reader2/ParserStateMachine.hpp @@ -89,6 +89,9 @@ namespace xo { /** get unique (within stringtable) string, beginning with @p prefix **/ const DUniqueString * gensym(std::string_view prefix); + /** get variable defn for @p symbolname, or else nullptr **/ + Binding lookup_binding(std::string_view symbolname); + /** push nested local symtab while parsing the body of a lambda expression; * restore previous symtab at the end of lambda-expression definition. * See @ref pop_local_symtab @@ -229,6 +232,11 @@ namespace xo { obj, std::string_view expect_str); + /** report error - no binding for variable @p sym + **/ + void error_unbound_variable(std::string_view ssm_name, + std::string_view sym); + ///@} private: diff --git a/src/reader2/DExpectExprSsm.cpp b/src/reader2/DExpectExprSsm.cpp index cddfb865..b1b0701b 100644 --- a/src/reader2/DExpectExprSsm.cpp +++ b/src/reader2/DExpectExprSsm.cpp @@ -3,23 +3,18 @@ * @author Roland Conybeare, Jan 2026 **/ -#include "DExpectExprSsm.hpp" +#include "ExpectExprSsm.hpp" #include "ParserStateMachine.hpp" #include "SyntaxStateMachine.hpp" -#include "ssm/ISyntaxStateMachine_DExpectExprSsm.hpp" #include "ssm/ISyntaxStateMachine_DProgressSsm.hpp" #include "DSequenceSsm.hpp" #include "syntaxstatetype.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -196,7 +191,27 @@ namespace xo { DExpectExprSsm::on_symbol_token(const Token & tk, ParserStateMachine * p_psm) { - Super::on_token(tk, p_psm); + scope log(XO_DEBUG(p_psm->debug_flag())); + + log && log(xtag("tk", tk)); + + const DVariable * var = p_psm->lookup_variable(tk.text()); + + if (!var) { + p_psm->error_unbound_variable(ssm_classname(), + tk.text()); + } + + // examples of possible continuations from symbol foo + // foo ; // (1) foo is entire rvalue expression + // foo + ... // (2) foo begin operator expression + // foo(..); // (3) foo begin apply function + // + // + + DProgressSsm::start(p_psm->parser_alloc(), + obj(const_cast(var)), + p_psm); } #ifdef NOT_YET diff --git a/src/reader2/DIfElseSsm.cpp b/src/reader2/DIfElseSsm.cpp index 7efb4a7e..0782f23a 100644 --- a/src/reader2/DIfElseSsm.cpp +++ b/src/reader2/DIfElseSsm.cpp @@ -68,8 +68,7 @@ namespace xo { obj expr_mm, ParserStateMachine * p_psm) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); DIfElseExpr * if_expr = DIfElseExpr::_make_empty(expr_mm); DIfElseSsm * if_ssm = DIfElseSsm::_make(parser_mm, if_expr); diff --git a/src/reader2/ParserStateMachine.cpp b/src/reader2/ParserStateMachine.cpp index 38f7ff4a..bd1effbb 100644 --- a/src/reader2/ParserStateMachine.cpp +++ b/src/reader2/ParserStateMachine.cpp @@ -107,6 +107,48 @@ namespace xo { return stringtable_.gensym(str); } + Binding + ParserStateMachine::lookup_binding(std::string_view symbolname) + { + scope log(XO_DEBUG(debug_flag_)); + + if (!local_symtab_) + return Binding::null(); + + const DUniqueString * ustr = stringtable_.lookup(symbolname); + + if (!ustr) { + // if not in string table, then can't be a variable either + return Binding::null(); + } + + DLocalSymtab * symtab = local_symtab_; + + // count #of nested scopes to cross, to reach symbol + // + int32_t link_count = 0; + + while (symtab) { + Binding b = symtab->lookup_binding(ustr); + + if (b.is_local()) { + assert(b.i_link() == 0); + + return Binding(link_count, b.j_slot()); + } + + ++link_count; + symtab = symtab->parent(); + } + + // TODO: check global symtab also + + log.retroactively_enable(); + log("STUB: check global symtab"); + + return Binding::null(); + } + void ParserStateMachine::push_local_symtab(DLocalSymtab * symtab) { @@ -400,6 +442,21 @@ namespace xo { this->capture_error(ssm_name, errmsg); } + + void + ParserStateMachine::error_unbound_variable(std::string_view ssm_name, + std::string_view sym) + { + auto errmsg_string = tostr("No binding for symbol", + xtag("symbol", sym), + xtag("ssm", ssm_name)); + + auto errmsg = DString::from_view(expr_alloc_, + std::string_view(errmsg_string)); + + this->capture_error(ssm_name, errmsg); + } + } /*namespace scm*/ } /*namespace xo*/ diff --git a/utest/SchematikaParser.test.cpp b/utest/SchematikaParser.test.cpp index 2fb10ea5..48983353 100644 --- a/utest/SchematikaParser.test.cpp +++ b/utest/SchematikaParser.test.cpp @@ -220,7 +220,7 @@ namespace xo { { const auto & testname = Catch::getResultCapture().getCurrentTestName(); - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); ArenaConfig config; @@ -285,7 +285,7 @@ namespace xo { { const auto & testname = Catch::getResultCapture().getCurrentTestName(); - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); ArenaConfig config; @@ -350,7 +350,7 @@ namespace xo { { const auto & testname = Catch::getResultCapture().getCurrentTestName(); - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); ArenaConfig config; @@ -415,7 +415,7 @@ namespace xo { { const auto & testname = Catch::getResultCapture().getCurrentTestName(); - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag), xtag("test", testname)); ArenaConfig config; @@ -517,7 +517,7 @@ namespace xo { TEST_CASE("SchematikaParser-interactive-lambda", "[reader2][SchematikaParser]") { - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag)); ArenaConfig config; @@ -725,7 +725,7 @@ namespace xo { TEST_CASE("SchematikaParser-interactive-if", "[reader2][SchematikaParser]") { - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; scope log(XO_DEBUG(c_debug_flag)); ArenaConfig config; @@ -834,6 +834,168 @@ namespace xo { //REQUIRE(result.error_description()); } + TEST_CASE("SchematikaParser-interactive-lambda2", "[reader2][SchematikaParser]") + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + ArenaConfig config; + config.name_ = "test-arena"; + config.size_ = 16 * 1024; + + DArena expr_arena = DArena::map(config); + obj expr_alloc = with_facet::mkobj(&expr_arena); + + SchematikaParser parser(config, 4096, expr_alloc, false /*debug_flag*/); + + parser.begin_interactive_session(); + + /** Walkthrough parsing input equivalent to: + * + * lambda (x : i64) -> i64 { x * x }; + * + **/ + + { + auto & result = parser.on_token(Token::lambda_token()); + + log && log("after lambda token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::leftparen_token()); + + log && log("after lparen token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::symbol_token("x")); + + log && log("after symbol(n) token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::colon_token()); + + log && log("after colon token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::symbol_token("i64")); + + log && log("after symbol(i64) token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::rightparen_token()); + + log && log("after rightparen token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::yields_token()); + + log && log("after yields token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::symbol_token("i64")); + + log && log("after symbol(i64) token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::leftbrace_token()); + + log && log("after leftbrace token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + + { + auto & result = parser.on_token(Token::symbol_token("x")); + + log && log("after symbol(x) token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == true); + REQUIRE(!result.is_error()); + REQUIRE(result.is_incomplete()); + } + +#ifdef NOPE + { + auto & result = parser.on_token(Token::rightbrace_token()); + + log && log("after rightbrace token:"); + log && log(xtag("parser", &parser)); + log && log(xtag("result", result)); + + REQUIRE(parser.has_incomplete_expr() == false); + REQUIRE(!result.is_error()); + REQUIRE(result.is_expression()); + REQUIRE(result.result_expr()); + } + + //REQUIRE(result.is_error()); + //// illegal input on token + //REQUIRE(result.error_description()); +#endif + REQUIRE(false); + } } /*namespace ut*/ } /*namespace xo*/