From 75b74918b7bf63f7526fbfc8ce658bb1a6449d05 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 26 Jul 2025 17:28:41 -0400 Subject: [PATCH] xo-expression xo-reader: type unifier + misc improvements --- xo-expression/CMakeLists.txt | 2 +- .../cmake/xo_expressionConfig.cmake.in | 1 + .../xo/expression/GeneralizedExpression.hpp | 2 +- .../include/xo/expression/Lambda.hpp | 2 + .../xo/expression/typeinf/TypeBlueprint.hpp | 89 +++++++ .../xo/expression/typeinf/type_ref.hpp | 36 ++- .../xo/expression/typeinf/type_unifier.hpp | 60 +++++ xo-expression/src/expression/CMakeLists.txt | 5 + xo-expression/src/expression/Lambda.cpp | 50 ++-- .../src/expression/typeinf/TypeBlueprint.cpp | 112 ++++++++ .../src/expression/typeinf/type_ref.cpp | 48 ++++ .../src/expression/typeinf/type_unifier.cpp | 218 ++++++++++++++++ xo-expression/utest/CMakeLists.txt | 10 + xo-expression/utest/expression_utest_main.cpp | 6 + xo-expression/utest/type_unifier.test.cpp | 239 ++++++++++++++++++ .../xo/indentlog/print/ppdetail_atomic.hpp | 8 +- .../include/xo/indentlog/print/pretty.hpp | 3 +- xo-reader/README | 6 +- xo-reader/include/xo/reader/formal_arg.hpp | 2 +- xo-reader/include/xo/reader/lambda_xs.hpp | 26 +- .../xo/reader/pretty_envframestack.hpp | 1 + .../xo/reader/pretty_parserstatemachine.hpp | 5 + xo-reader/include/xo/reader/sequence_xs.hpp | 4 + xo-reader/src/reader/expect_expr_xs.cpp | 6 +- xo-reader/src/reader/exprstate.cpp | 6 +- xo-reader/src/reader/exprstatestack.cpp | 1 + xo-reader/src/reader/lambda_xs.cpp | 98 +++++-- xo-reader/src/reader/let1_xs.cpp | 1 + .../src/reader/pretty_parserstatemachine.cpp | 14 + xo-reader/src/reader/sequence_xs.cpp | 16 +- xo-reflect/include/xo/reflect/TypeDescr.hpp | 12 +- 31 files changed, 1009 insertions(+), 80 deletions(-) create mode 100644 xo-expression/include/xo/expression/typeinf/TypeBlueprint.hpp create mode 100644 xo-expression/include/xo/expression/typeinf/type_unifier.hpp create mode 100644 xo-expression/src/expression/typeinf/TypeBlueprint.cpp create mode 100644 xo-expression/src/expression/typeinf/type_ref.cpp create mode 100644 xo-expression/src/expression/typeinf/type_unifier.cpp create mode 100644 xo-expression/utest/CMakeLists.txt create mode 100644 xo-expression/utest/expression_utest_main.cpp create mode 100644 xo-expression/utest/type_unifier.test.cpp diff --git a/xo-expression/CMakeLists.txt b/xo-expression/CMakeLists.txt index 468778de..8edb8ed8 100644 --- a/xo-expression/CMakeLists.txt +++ b/xo-expression/CMakeLists.txt @@ -26,7 +26,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # docs targets depend on all the other library/utest targets diff --git a/xo-expression/cmake/xo_expressionConfig.cmake.in b/xo-expression/cmake/xo_expressionConfig.cmake.in index e7f7f1be..1560b468 100644 --- a/xo-expression/cmake/xo_expressionConfig.cmake.in +++ b/xo-expression/cmake/xo_expressionConfig.cmake.in @@ -2,5 +2,6 @@ include(CMakeFindDependencyMacro) find_dependency(reflect) +find_dependency(xo_flatstring) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/xo-expression/include/xo/expression/GeneralizedExpression.hpp b/xo-expression/include/xo/expression/GeneralizedExpression.hpp index 1d513a2d..552d26a9 100644 --- a/xo-expression/include/xo/expression/GeneralizedExpression.hpp +++ b/xo-expression/include/xo/expression/GeneralizedExpression.hpp @@ -31,7 +31,7 @@ namespace xo { exprtype extype() const { return extype_; } TypeDescr valuetype() const { return valuetype_; } - /** write human-readable representation to stream **/ + /** write human-readable representation to stream @p os **/ virtual void display(std::ostream & os) const = 0; /** human-readable string representation **/ virtual std::string display_string() const; diff --git a/xo-expression/include/xo/expression/Lambda.hpp b/xo-expression/include/xo/expression/Lambda.hpp index 380eeb9f..636dfbc0 100644 --- a/xo-expression/include/xo/expression/Lambda.hpp +++ b/xo-expression/include/xo/expression/Lambda.hpp @@ -40,6 +40,7 @@ namespace xo { **/ static rp make_from_env(const std::string & name, const rp & env, + TypeDescr explicit_return_td, const rp & body); /** downcast from Expression **/ @@ -105,6 +106,7 @@ namespace xo { * and body expression @p body **/ static TypeDescr assemble_lambda_td(const std::vector> & argv, + TypeDescr explicit_return_td, const rp & body); /** create string description for function signature, diff --git a/xo-expression/include/xo/expression/typeinf/TypeBlueprint.hpp b/xo-expression/include/xo/expression/typeinf/TypeBlueprint.hpp new file mode 100644 index 00000000..b3cac36e --- /dev/null +++ b/xo-expression/include/xo/expression/typeinf/TypeBlueprint.hpp @@ -0,0 +1,89 @@ +/** @file TypeBlueprint.hpp **/ + +#include "xo/refcnt/Refcounted.hpp" +#include "type_ref.hpp" +#include +#include + +namespace xo { + namespace scm { + class TypeBlueprint; + + /** map from a type variable, to contraints on the resolution of that variable **/ + using type_substitution_map = std::map>; + + /** @class TypeBlueprint + * @brief record constraints on a type variable. + * + * Within type unification, a TypeBlueprint represents + * current state of knowledge as to the resolution of a particular type. + * + * Structurally homologous to @ref xo::reflect::TypeDescr, + * but TypeDescr is intended to represent fully-defined types. + * Conversely TypeBlueprint instances will be abandoned once + * a corresponding TypeDescr exists. + **/ + class TypeBlueprint : public xo::ref::Refcount { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + TypeBlueprint() = default; + + /** contruct blueprint for type_ref @p ref **/ + static rp make(const type_ref& ref); + /** contruct blueprint for type variable @p name. + * equivalent to @c make(type_ref(name, nullptr)) + **/ + static rp typevar(const type_var& name); + + /** compare two blueprints for equality. + * blueprints are equal iff (we know that) they refer to the same concrete type. + **/ + static bool equals(bp lhs, bp rhs); + + const type_ref& ref() const { return ref_; } + const type_var& id() const { return ref_.id(); } + TypeDescr td() const { return ref_.td(); } + + bool is_concrete() const { return ref_.is_concrete(); } + bool is_variable() const; + + /** upsert into @p *p_typevarset all unresolved type variables **/ + void upsert_typevars(std::set * p_typevar_set) const; + + /** apply substitutions in @p sub_map to this type **/ + bp substitute(const type_substitution_map& sub_map); + + /** replace with resolved type description. + * Promise: + * 1. ref().td() == @p td + * 2. this->is_concrete() == true + * 3. this->is_variable() == false + **/ + void resolve_to(TypeDescr td); + + /** write human-readable representation to stream @p os **/ + void display(std::ostream & os) const; + + private: + /** construct blueprint for @p ref **/ + explicit TypeBlueprint(const type_ref & ref); + + private: + /** name of the type being constrained here **/ + type_ref ref_; + + // additional descriptive info.. + }; + + inline std::ostream & + operator<<(std::ostream & os, const TypeBlueprint & x) { + x.display(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end TypeBlueprint.hpp **/ diff --git a/xo-expression/include/xo/expression/typeinf/type_ref.hpp b/xo-expression/include/xo/expression/typeinf/type_ref.hpp index 4f65ce98..b158a005 100644 --- a/xo-expression/include/xo/expression/typeinf/type_ref.hpp +++ b/xo-expression/include/xo/expression/typeinf/type_ref.hpp @@ -2,18 +2,48 @@ #pragma once +#include "xo/flatstring/flatstring.hpp" +#include "xo/reflect/TypeDescr.hpp" + namespace xo { namespace scm { - using type_var = xo::flatstring<11>; + using prefix_type = xo::flatstring<8>; + using type_var = xo::flatstring<16>; /** @class type_ref * @brief name and eventual resolution for type associated with an expression. * - * + * Type inference / unification operates on @ref xo::scm::TypeTemplate instances, see also. **/ - struct type_ref { + class type_ref { + public: + using TypeDescr = xo::reflect::TypeDescr; + public: + type_ref() = default; + type_ref(const type_var& id, TypeDescr td); + /** generate a unique type-variable name, that begins with @p prefix **/ + static type_var generate_unique(prefix_type prefix); + + const type_var& id() const { return id_; } + TypeDescr td() const { return td_; } + + /** true iff type at this location has been resolved **/ + bool is_concrete() const; + + void resolve_to(TypeDescr td); + + private: + /** unique (likely generated) name for type at this location **/ + type_var id_; + /** description for concrete type, once resolved. + * may be null when type_ref created. + * expected to be immutable once established. + * note that TypeDescr itself may be incomplete, + * but not for inference purposes + **/ + TypeDescr td_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/xo-expression/include/xo/expression/typeinf/type_unifier.hpp b/xo-expression/include/xo/expression/typeinf/type_unifier.hpp new file mode 100644 index 00000000..22cb53bb --- /dev/null +++ b/xo-expression/include/xo/expression/typeinf/type_unifier.hpp @@ -0,0 +1,60 @@ +/** @file type_unifier.hpp **/ + +#include "type_ref.hpp" +#include "TypeBlueprint.hpp" +#include + +namespace xo { + namespace scm { + struct unify_result { + /** true iff unification success **/ + bool success_; + /** blueprint (possibly concrete) for unified type **/ + rp unified_; + /** if @ref success_ is false -> non-null source function + * in which contradiction detected + **/ + const char * error_src_function_ = nullptr; + /** if @ref success_ is false -> human-readable error description **/ + std::string error_description_; + }; + + std::ostream & operator<< (std::ostream & os, const unify_result & x); + + /** @class type_unifer + * @brief type unification algorithm + **/ + class type_unifier { + public: + type_unifier() = default; + + /** error message where unification would require both + * 1. equals(s1,t2) + * 2. t2 contains s1 + **/ + static unify_result occurs_error(const char * src_function, + bp t1, + bp t2, + bp s1, + bp s2); + + /** given fact that @p lhs and @p rhs must refer to + * the same type: + * 1. unify their type blueprints to get new blueprint U(lhs.rhs) + * 2. update @ref constraint_map_ so that typevar ids for + * @p lhs and @p rhs refer to U(lhs,rhs) + * 3. also update @ref constraint_map_ for any secondary unifications + * that are discovered + * 4. return error if unification is contradiction encountered. + **/ + unify_result unify(bp lhs, bp rhs); + + private: + type_substitution_map constraint_map_; + }; + + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end type_unifier.hpp **/ diff --git a/xo-expression/src/expression/CMakeLists.txt b/xo-expression/src/expression/CMakeLists.txt index efcb9cdd..57ba0b03 100644 --- a/xo-expression/src/expression/CMakeLists.txt +++ b/xo-expression/src/expression/CMakeLists.txt @@ -15,10 +15,15 @@ set(SELF_SRCS LocalEnv.cpp ConvertExpr.cpp Primitive.cpp + typeinf/type_ref.cpp + typeinf/type_unifier.cpp + typeinf/TypeBlueprint.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +# note: deps here must also appear in cmake/xo_expressionConfig.cmake.in xo_dependency(${SELF_LIB} reflect) +xo_dependency(${SELF_LIB} xo_flatstring) #xo_dependency(${SELF_LIB} indentlog) #xo_dependency(${SELF_LIB} subsys) diff --git a/xo-expression/src/expression/Lambda.cpp b/xo-expression/src/expression/Lambda.cpp index 80e30e67..eafb5d3b 100644 --- a/xo-expression/src/expression/Lambda.cpp +++ b/xo-expression/src/expression/Lambda.cpp @@ -20,6 +20,7 @@ namespace xo { namespace ast { TypeDescr Lambda::assemble_lambda_td(const std::vector> & argv, + TypeDescr explicit_return_td, const rp & body) { if (!body) @@ -39,8 +40,16 @@ namespace xo { } } + if (explicit_return_td && body->valuetype() && (explicit_return_td != body->valuetype())) { + throw std::runtime_error(tostr("explicit lambda return type T1 conflicts with lambda body T2", + xtag("T1", explicit_return_td), + xtag("T2", body->valuetype()))); + } + + // TODO: unify(explicit_return_td, body->valuetype()) + auto function_info - = FunctionTdxInfo(body->valuetype(), + = FunctionTdxInfo(explicit_return_td ? explicit_return_td : body->valuetype(), arg_td_v, false /*!is_noexcept*/); @@ -72,9 +81,10 @@ namespace xo { rp Lambda::make_from_env(const std::string & name, const rp & env, + TypeDescr explicit_return_td, const rp & body) { - TypeDescr lambda_td = assemble_lambda_td(env->argv(), body); + TypeDescr lambda_td = assemble_lambda_td(env->argv(), explicit_return_td, body); rp retval = new Lambda(name, @@ -96,7 +106,9 @@ namespace xo { { rp env = LocalEnv::make(argv, parent_env); - return make_from_env(name, env, body); + TypeDescr explicit_return_td = nullptr; + + return make_from_env(name, env, explicit_return_td, body); } /*make*/ std::set @@ -168,8 +180,9 @@ namespace xo { void Lambda::complete_assembly_from_body() { if (body_) { + TypeDescr explicit_return_td = nullptr; TypeDescr lambda_td - = assemble_lambda_td(this->local_env_->argv(), body_); + = assemble_lambda_td(this->local_env_->argv(), explicit_return_td, body_); if (lambda_td) this->type_str_ = assemble_type_str(lambda_td); @@ -328,32 +341,6 @@ namespace xo { refrtag("name", name_), refrtag("argv", local_env_->argv()), refrtag("body", body_)); - -#ifdef OBSOLETE - ppstate * pps = ppii.pps(); - - if (ppii.upto()) { - if (!pps->print_upto("print_upto_tag("name", name_)) - return false; - if (!pps->print_upto_tag("argv", local_env_->argv())) - return false; - if (!pps->print_upto_tag("body", body_)) - return false; - pps->write(">"); - - return true; - } else { - pps->write("newline_pretty_tag(ppii.ci1(), "name", name_); - pps->newline_pretty_tag(ppii.ci1(), "argv", local_env_->argv()); - pps->newline_pretty_tag(ppii.ci1(), "body", body_); - pps->write(">"); - - return false; - } -#endif } // ----- Lambda Access ----- @@ -364,7 +351,8 @@ namespace xo { const rp & body, const rp & parent_env) { - TypeDescr lambda_td = assemble_lambda_td(argv, body); + TypeDescr explicit_return_td = nullptr; + TypeDescr lambda_td = assemble_lambda_td(argv, explicit_return_td, body); rp env = LocalEnv::make(argv, parent_env); rp retval diff --git a/xo-expression/src/expression/typeinf/TypeBlueprint.cpp b/xo-expression/src/expression/typeinf/TypeBlueprint.cpp new file mode 100644 index 00000000..4f1573ab --- /dev/null +++ b/xo-expression/src/expression/typeinf/TypeBlueprint.cpp @@ -0,0 +1,112 @@ +/** @file TypeBlueprint.cpp **/ + +#include "typeinf/TypeBlueprint.hpp" + +namespace xo { + namespace scm { + TypeBlueprint::TypeBlueprint(const type_ref & x) + : ref_{x} + {} + + rp + TypeBlueprint::make(const type_ref & ref) + { + return new TypeBlueprint(ref); + } + + rp + TypeBlueprint::typevar(const type_var & name) + { + return new TypeBlueprint(type_ref(name, nullptr)); + } + + bool + TypeBlueprint::equals(bp lhs, bp rhs) + { + // 1. two concrete blueprints are equal if they resolve to the same type. + // 2. two type variables are equal if they have the same unique name; + // but: once we introduce structural constraints will relax this + + if (lhs->is_concrete() && rhs->is_concrete()) + { + return lhs->td() == rhs->td(); + } + + if (lhs->id() == rhs->id()) + { + // typevar names are globally unique, + // so two typevars with the same name must refer to the same type + return true; + } + + // TODO: structural comparisons.. + + return false; + } + + bool + TypeBlueprint::is_variable() const + { + // TODO; + // if we have structural information about this type, + // e.g. vector[t'] or function(a' -> b'), + // then must return false here + + return !ref_.is_concrete(); + } + + void + TypeBlueprint::upsert_typevars(std::set * p_typevar_set) const + { + if (this->is_concrete()) { + return; + } + + // TODO: handle structural types + + p_typevar_set->insert(ref_.id()); + } + bp + TypeBlueprint::substitute(const type_substitution_map& sub_map) + { + bp subject = this; + + // loop here should only run once. + // we collapse sub_map whenever we extend it. + // + while(!subject->is_concrete()) { + auto ix = sub_map.find(subject->id()); + + if (ix == sub_map.end()) + break; + + subject = ix->second.get(); + } + + // TODO: also want to update the whole chain, + // so that everything refers to final subjectc + + return subject; + } + + void + TypeBlueprint::resolve_to(TypeDescr td) + { + ref_.resolve_to(td); + } + + void + TypeBlueprint::display(std::ostream & os) const + { + os << "canonical_name()); + os << ">"; + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end TypeBlueprint.cpp **/ diff --git a/xo-expression/src/expression/typeinf/type_ref.cpp b/xo-expression/src/expression/typeinf/type_ref.cpp new file mode 100644 index 00000000..8df033ae --- /dev/null +++ b/xo-expression/src/expression/typeinf/type_ref.cpp @@ -0,0 +1,48 @@ +/** @file type_ref.cpp **/ + +#include "typeinf/type_ref.hpp" + +namespace xo { + namespace scm { + + type_ref::type_ref(const type_var& id, TypeDescr td) + : id_{id}, td_{td} + {} + + bool type_ref::is_concrete() const { return td_ != nullptr; } + + auto + type_ref::generate_unique(xo::scm::prefix_type prefix) -> xo::scm::type_var + { + static uint32_t s_counter = 0; + + s_counter = (s_counter + 1) % 100000000; + + char buf [type_var::fixed_capacity]; + int n = snprintf(buf, sizeof(buf), "%s:%u", prefix.c_str(), s_counter); + + assert(n < static_cast(type_var::fixed_capacity)); + + // not necessary, but remove all doubt + // max: + // 7 chars for prefix + // 8 chars for u32 % 1000000000 + // + buf [type_var::fixed_capacity - 1] = '\0'; + + return buf; + } + + void + type_ref::resolve_to(TypeDescr td) + { + assert(!td_); + + this->td_ = td; + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end type_ref.cpp **/ diff --git a/xo-expression/src/expression/typeinf/type_unifier.cpp b/xo-expression/src/expression/typeinf/type_unifier.cpp new file mode 100644 index 00000000..4f73718b --- /dev/null +++ b/xo-expression/src/expression/typeinf/type_unifier.cpp @@ -0,0 +1,218 @@ +/** @file type_unifier.cpp + * + * author: Roland Conybeare, Jul 2025 + **/ + +#include "typeinf/type_unifier.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + namespace scm { + std::ostream & + operator<< (std::ostream & os, + const unify_result & x) + { + os << ""; + return os; + } + + unify_result + type_unifier::occurs_error(const char * src_function, + bp t1, + bp t2, + bp s1, + bp s2) + { + // unification implies some infinite type, + // e.g. unify a' with (i64 -> 'a) + // would imply type (i64 -> i64 -> i64 -> ...) + return { + .success_ = false, + .unified_ = nullptr, + .error_src_function_ = src_function, + .error_description_ = tostr("attempting unify(T1,T2) with T1 -> S1, T2 -> S2", + ": occurs check failed with S1 occuring in S2", + xrefrtag("T1", t1), + xrefrtag("T2", t2), + xrefrtag("S1", s1), + xrefrtag("S2", s2)) + }; + }; + + unify_result + type_unifier::unify(bp lhs, bp rhs) + { + /** if we already have substitutions for either of {lhs, rhs}, use them **/ + + auto lhs1 = lhs->substitute(constraint_map_); + auto rhs1 = rhs->substitute(constraint_map_); + + /** reminder: + * 1. lhs1, rhs1 need not be in constraint_map, + * 2. lhs1, rhs1 need not be distinct from lhs, rhs respectively + **/ + + if (TypeBlueprint::equals(lhs1, rhs1)) { + // blueprints are already equivalent on their face. + // this recognizes matching concrete types. + // + // return the lexicographically earlier id as canonical representative + bp canonical = (lhs1->id() < rhs1->id()) ? lhs1 : rhs1; + + return { + .success_ = true, + .unified_ = canonical.promote(), + .error_src_function_ = nullptr, + .error_description_ = "" + }; + } + + assert(lhs1->id() != rhs1->id()); + + constexpr const char * c_self_name = "type_unifier::unify"; + + bp canonical; + + /** if both lhs1 and rhs1 are type variables, + * pick the lexicographically earlier one as canonical name. + * (already know they're distinct because did not satisfy equality test above) + * + * prefer the canonical name as rhs target of all substitutions + * from known-to-be-equivalent typevars. + **/ + if (lhs1->is_variable()) + { + if (rhs1->is_variable()) + { + // haven't resolved anything yet, but we do know + // that type variables lhs,rhs,lhs1,rhs1 must refer to the same type + + if (lhs1->ref().id() < rhs1->ref().id()) { + canonical = lhs1; + constraint_map_[rhs1->id()] = lhs1.promote(); + } else { + canonical = rhs1; + constraint_map_[lhs1->id()] = rhs1.promote(); + } + } else if (rhs1->is_concrete()) { + canonical = (lhs1->id() < rhs1->id()) ? lhs1 : rhs1; + + // update lhs, lhs1 to refer to resolved rhs1. + // rhs would already have been resolved + assert(rhs->td() == rhs1->td()); + + lhs1->resolve_to(rhs1->td()); + if (lhs->id() != lhs1->id()) + lhs->resolve_to(rhs1->td()); + } else { + // 1. lhs1->is_variable() + // 2. !rhs1->is_variable() && !rhs1->is_concrete() + // + // therefore need occurs check for lhs1 appearing in rhs1 + + std::set rhs1_typevar_set; + rhs1->upsert_typevars(&rhs1_typevar_set); + + if (rhs1_typevar_set.contains(lhs1->id())) { + return type_unifier::occurs_error(c_self_name, + lhs, rhs, lhs1, rhs1); + } + + // TODO: some sort of recursive unification here + assert(false); + } + } else if (rhs1->is_variable()) + { + assert(!rhs1->is_concrete()); + + if (lhs1->is_concrete()) + { + canonical = (lhs1->id() < rhs1->id()) ? lhs1 : rhs1; + + // update rhs, rhs1 to refer to resolved lhs1. + // lhs would already have been resolved + assert(lhs->td() == lhs1->td()); + + rhs1->resolve_to(lhs1->td()); + if (rhs->td() != rhs1->td()) + rhs->resolve_to(lhs1->td()); + } else + { + // 1. !lhs1->is_variable() && !lhs1->is_concrete() + // 2. rhs1->is_variable() + // + // Need occurs check for rhs1 appearing in lhs1 + + std::set lhs1_typevar_set; + lhs1->upsert_typevars(&lhs1_typevar_set); + + if (lhs1_typevar_set.contains(rhs1->id())) { + return type_unifier::occurs_error(c_self_name, + rhs, lhs, rhs1, lhs1); + } + + // TODO: some sort of recursive unification here + assert(false); + } + } else if (lhs1->is_concrete() && rhs1->is_concrete()) + { + /* we already know lhs1 != rhs1 -> unification failure */ + return { + .success_ = false, + .unified_ = nullptr, + .error_src_function_ = c_self_name, + .error_description_ = tostr("attempting unify(T1,T2) with T1 -> S1, T2 -> S2", + ": incompatible concrete types S1,S2", + xrefrtag("T1", lhs), + xrefrtag("T2", rhs), + xrefrtag("S1", lhs1), + xrefrtag("S2", rhs1)) + }; + } + + // TODO: recursive unification for structural types, function types etc. + + if (canonical) + { + constraint_map_[lhs1->id()] = canonical.promote(); + constraint_map_[rhs1->id()] = canonical.promote(); + + if (!constraint_map_.contains(lhs1->id())) + constraint_map_[lhs1->id()] = canonical.promote(); + if (!constraint_map_.contains(rhs1->id())) + constraint_map_[rhs1->id()] = canonical.promote(); + + return { + .success_ = true, + .unified_ = canonical.promote(), + .error_src_function_ = nullptr, + .error_description_ = "" + }; + } + + assert(false); + + return { + .success_ = false, + .unified_ = nullptr, + .error_src_function_ = c_self_name, + .error_description_ = tostr("attempting unify(T1,T2) with T1 -> S1, T2 -> S2", + "supposedly-unreachable case for S1,S2", + xrefrtag("T1", lhs), + xrefrtag("T2", rhs), + xrefrtag("S1", lhs1), + xrefrtag("S2", rhs1)) + }; + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/** end type_unifier.cpp **/ diff --git a/xo-expression/utest/CMakeLists.txt b/xo-expression/utest/CMakeLists.txt new file mode 100644 index 00000000..19dbb944 --- /dev/null +++ b/xo-expression/utest/CMakeLists.txt @@ -0,0 +1,10 @@ +# build unittest expression/utest + +set(SELF_EXE utest.expression) +set(SELF_SRCS + expression_utest_main.cpp + type_unifier.test.cpp) + +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} xo_expression) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/xo-expression/utest/expression_utest_main.cpp b/xo-expression/utest/expression_utest_main.cpp new file mode 100644 index 00000000..7ca3b66a --- /dev/null +++ b/xo-expression/utest/expression_utest_main.cpp @@ -0,0 +1,6 @@ +/* file expression_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end expression_utest_main.cpp */ diff --git a/xo-expression/utest/type_unifier.test.cpp b/xo-expression/utest/type_unifier.test.cpp new file mode 100644 index 00000000..c670ae6b --- /dev/null +++ b/xo-expression/utest/type_unifier.test.cpp @@ -0,0 +1,239 @@ +/* @file expression.text.cppreference + * + * author: Roland Conybeare, Jul 2025 + */ + +#include "xo/expression/typeinf/type_unifier.hpp" +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + namespace ut { + // rehearser copied from xo_tokenizer/utest/tokenizer.test.cpp + + /** Two-pass test harness. + * + * First pass - verify test assertions. + * Second pass only if first pass failed. + * On second pass, enable verbose logging + **/ + struct rehearser { + rehearser(std::uint32_t att = 0) : attention_{att} {} + + /* expect at most one iterator to exist per TestRehearser instance **/ + struct iterator { + explicit iterator(rehearser* parent) : parent_{parent} {} + + iterator& operator++(); + std::uint32_t operator*() { return parent_->attention_; } + + bool operator==(const iterator& ix2) const { + return (parent_ == ix2.parent_); + } + + rehearser* parent_ = nullptr; + std::uint32_t attention_ = 0; + + }; + + bool is_first_pass() const { return attention_ == 0; } + bool is_second_pass() const { return attention_ == 1; } + bool enable_debug() const { return is_second_pass(); } + + iterator begin() { return iterator(this); } + iterator end() { return iterator(nullptr); } + + public: + /** pass number: 0 or 1 **/ + std::uint32_t attention_ = 0; + /** @brief set to true when test starts; false if first pass fails **/ + bool ok_flag_ = true; + }; + + auto rehearser::iterator::operator++() -> iterator& + { + if (parent_) + ++(parent_->attention_); + + if (parent_->ok_flag_ && (parent_->attention_ == 1)) { + /* skip 2nd pass */ + ++(parent_->attention_); + } + + if (parent_->attention_ == 2) + parent_ = nullptr; + + return *this; + } + + /* use this instead of REQUIRE(expr) in context of a test_rehearser + * REQUIRE(true) in first pass so we count assertions + */ +# define REHEARSE(rehearser, expr) \ + if (rehearser.is_first_pass()) { \ + REQUIRE(true); \ + bool _f = (expr); \ + rehearser.ok_flag_ = rehearser.ok_flag_ && _f; \ + } else { \ + REQUIRE(expr); \ + } + + /* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + + using xo::scm::type_unifier; + using xo::scm::TypeBlueprint; + using xo::scm::unify_result; + using xo::scm::type_ref; + using xo::scm::type_var; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + + namespace { + struct testcase_ufy { + /* using lambda's here to ensure don't share state between test loop iterations below */ + std::function()> lhs_; + std::function()> rhs_; + + bool expect_unify_ok_ = false; + type_var expect_unify_id_; + bool expect_unify_concrete_ = false; + std::string expect_concrete_typename_; + bool expect_unify_variable_ = false; + }; + + std::vector + s_testcase_v = { + /* unify two variables */ + { + .lhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("a")); }, + .rhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("b")); }, + .expect_unify_ok_ = true, + .expect_unify_id_{type_var::from_chars("a")}, + .expect_unify_concrete_ = false, + .expect_concrete_typename_ = "", + .expect_unify_variable_ = true, + }, + /* unify two variables (different order) */ + { + .lhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("b")); }, + .rhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("a")); }, + .expect_unify_ok_ = true, + .expect_unify_id_{type_var::from_chars("a")}, + .expect_unify_concrete_ = false, + .expect_concrete_typename_ = "", + .expect_unify_variable_ = true, + }, + /* unify a variable with a concrete type */ + { + .lhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("a")); }, + .rhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("b"), + Reflect::require())); }, + .expect_unify_ok_ = true, + .expect_unify_id_{type_var::from_chars("a")}, + .expect_unify_concrete_ = true, + .expect_concrete_typename_ = "long int", + .expect_unify_variable_ = false, + }, + /* same, but reverse order */ + { + .lhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("a"), + Reflect::require())); }, + .rhs_ = [](){ return TypeBlueprint::typevar(type_var::from_chars("b")); }, + .expect_unify_ok_ = true, + .expect_unify_id_{type_var::from_chars("a")}, + .expect_unify_concrete_ = true, + .expect_concrete_typename_ = "long int", + .expect_unify_variable_ = false, + }, + /* matching concrete types */ + { + .lhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("a"), + Reflect::require())); }, + .rhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("b"), + Reflect::require())); }, + .expect_unify_ok_ = true, + .expect_unify_id_{type_var::from_chars("a")}, + .expect_unify_concrete_ = true, + .expect_concrete_typename_ = "bool", + .expect_unify_variable_ = false, + }, + + /* conflicting concrete types */ + { + .lhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("a"), + Reflect::require())); }, + .rhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("b"), + Reflect::require())); }, + .expect_unify_ok_ = false, + // remainder ignored + .expect_unify_id_{}, + .expect_unify_concrete_{}, + .expect_concrete_typename_{}, + .expect_unify_variable_{}, + } + }; + } + + TEST_CASE("unifier", "[type-unification]") + { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + const testcase_ufy & testcase = s_testcase_v[i_tc]; + + rehearser rh; + CHECK(rh.ok_flag_ == true); + + for (auto _ : rh) { + /* this loop problematic because iterations aren't independent: + * TypeBlueprint instances modified in place by unification + */ + + scope log(XO_DEBUG2(rh.enable_debug(), "unifier")); + + auto lhs = testcase.lhs_(); + auto rhs = testcase.rhs_(); + + log && log(xtag("i_tc", i_tc), + xtag("lhs", lhs), + xtag("rhs", rhs)); + + type_unifier unifier; + + auto ur = unifier.unify(lhs, rhs); + + log && log(xtag("ur", ur)); + + REHEARSE(rh, ur.success_ == testcase.expect_unify_ok_); + if (testcase.expect_unify_ok_) { + REHEARSE(rh, ur.unified_.get() != nullptr); + if (ur.unified_) { + REHEARSE(rh, ur.unified_->id() == testcase.expect_unify_id_); + REHEARSE(rh, ur.unified_->is_concrete() == testcase.expect_unify_concrete_); + if (ur.unified_->is_concrete()) { + REHEARSE(rh, ur.unified_->td()->canonical_name() == testcase.expect_concrete_typename_); + } + REHEARSE(rh, ur.unified_->is_variable() == testcase.expect_unify_variable_); + } + } + //REHEARSE(rh, ur.error_description_ == testcase.expect_unify_error_); + } + } + } + + + } /*namespace ut*/ +} /*namespace xo*/ diff --git a/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp b/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp index d8036fe6..3a14399b 100644 --- a/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -13,10 +13,10 @@ namespace xo { struct ppstate; // see pretty.hpp struct ppindentinfo; -// Defining this means ppdetail_atomic is not used. -// In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type, -// instead of giving compile-time error about missing template specialization of ppdetail. -#define ppdetail_atomic ppdetail + // Defining this means ppdetail_atomic is not used. + // In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type, + // instead of giving compile-time error about missing template specialization of ppdetail. +//#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) diff --git a/xo-indentlog/include/xo/indentlog/print/pretty.hpp b/xo-indentlog/include/xo/indentlog/print/pretty.hpp index 9f17d64d..aa50ca3c 100644 --- a/xo-indentlog/include/xo/indentlog/print/pretty.hpp +++ b/xo-indentlog/include/xo/indentlog/print/pretty.hpp @@ -440,7 +440,8 @@ namespace xo { ppstate & ppstate::newline_pretty_tag(std::uint32_t ci, Name && name, Value && value) { - newline_indent(ci); + this->newline_indent(ci); + this->current_indent_ = ci; this->pretty(rtag(name, value)); return *this; diff --git a/xo-reader/README b/xo-reader/README index 59a6e677..cee8b46c 100644 --- a/xo-reader/README +++ b/xo-reader/README @@ -17,7 +17,7 @@ a. create DefineExpr with TypeVariable. // compose these into Expressions. // -struct TypeTemplateRef { +struct type_ref { bool is_concrete() const { return td_ && td_->is_concrete(); } // generated name, so we can map between types. Don't want to create TypeDescr @@ -29,10 +29,10 @@ struct TypeTemplateRef { // what we know about a type // -struct TypeTemplate : public Refcounted { +struct TypeBlueprint : public Refcounted { static bool equal(bp lhs, bp rhs); - TypeTemplateRef ref_; + type_ref ref_; // additional descriptive info... }; diff --git a/xo-reader/include/xo/reader/formal_arg.hpp b/xo-reader/include/xo/reader/formal_arg.hpp index 4f0afaef..a94f45eb 100644 --- a/xo-reader/include/xo/reader/formal_arg.hpp +++ b/xo-reader/include/xo/reader/formal_arg.hpp @@ -42,7 +42,7 @@ namespace xo { /** formal parameter name **/ std::string name_; /** type description for variable @p name **/ - TypeDescr td_; + TypeDescr td_ = nullptr; }; inline std::ostream & diff --git a/xo-reader/include/xo/reader/lambda_xs.hpp b/xo-reader/include/xo/reader/lambda_xs.hpp index 3f49b973..0832add2 100644 --- a/xo-reader/include/xo/reader/lambda_xs.hpp +++ b/xo-reader/include/xo/reader/lambda_xs.hpp @@ -12,15 +12,20 @@ namespace xo { namespace scm { /** - * lambda ( name(1) : type(1), ..., ) body-expr ; - * ^ ^ ^ ^ - * | | | | - * lm_0 lm_1 lm_2 lm_3 + * @text + * + * lambda ( name(1) : type(1), ..., ) : type body-expr ; + * ^ ^ ^ ^ ^ ^ + * | | | | lm_4 lm_5 + * | | | lm_3 + * lm_0 lm_1 lm_2 * * lm_0 --on_lambda_token()--> lm_1 * lm_1 --on_formal_arglist()--> lm_2 * lm_2 --on_expr()--> lm_3 - * lm_3 --on_semicolon_token()--> (done) + * lm_5 --on_semicolon_token()--> (done) + * + * @endtext **/ enum class lambdastatetype { invalid = -1, @@ -29,6 +34,8 @@ namespace xo { lm_1, lm_2, lm_3, + lm_4, + lm_5, n_lambdastatetype }; @@ -60,12 +67,18 @@ namespace xo { virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void on_typedescr(TypeDescr td, + parserstatemachine * p_psm) override; virtual void on_formal_arglist(const std::vector> & argl, parserstatemachine * p_psm) override; virtual void on_expr(bp expr, parserstatemachine * p_psm) override; virtual void on_expr_with_semicolon(bp expr, parserstatemachine * p_psm) override; + virtual void on_leftbrace_token(const token_type & tk, + parserstatemachine * p_psm) override; + virtual void on_colon_token(const token_type & tk, + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) override; @@ -81,6 +94,9 @@ namespace xo { /** lambda environment (for formal parameters) **/ rp local_env_; + /** explicit return type (if supplied) **/ + TypeDescr explicit_return_td_ = nullptr; + /** body expression **/ rp body_; diff --git a/xo-reader/include/xo/reader/pretty_envframestack.hpp b/xo-reader/include/xo/reader/pretty_envframestack.hpp index 043b0eb2..be890b59 100644 --- a/xo-reader/include/xo/reader/pretty_envframestack.hpp +++ b/xo-reader/include/xo/reader/pretty_envframestack.hpp @@ -16,5 +16,6 @@ namespace xo { return p->pretty_print(ppii); } }; + } /*namespace print*/ } /*namespace xo*/ diff --git a/xo-reader/include/xo/reader/pretty_parserstatemachine.hpp b/xo-reader/include/xo/reader/pretty_parserstatemachine.hpp index 512a6844..50e6737f 100644 --- a/xo-reader/include/xo/reader/pretty_parserstatemachine.hpp +++ b/xo-reader/include/xo/reader/pretty_parserstatemachine.hpp @@ -14,5 +14,10 @@ namespace xo { struct ppdetail { static bool print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine & x); }; + + template<> + struct ppdetail { + static bool print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine * x); + }; } } /*namespace xo*/ diff --git a/xo-reader/include/xo/reader/sequence_xs.hpp b/xo-reader/include/xo/reader/sequence_xs.hpp index fd16ffb5..22e9a325 100644 --- a/xo-reader/include/xo/reader/sequence_xs.hpp +++ b/xo-reader/include/xo/reader/sequence_xs.hpp @@ -26,10 +26,14 @@ namespace xo { virtual void on_expr(bp expr, parserstatemachine * p_psm) override; + virtual void on_expr_with_semicolon(bp expr, + parserstatemachine * p_psm) override; virtual void on_rightbrace_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void print(std::ostream & os) const override; + private: sequence_xs(); diff --git a/xo-reader/src/reader/expect_expr_xs.cpp b/xo-reader/src/reader/expect_expr_xs.cpp index e899f3cb..68f5a12d 100644 --- a/xo-reader/src/reader/expect_expr_xs.cpp +++ b/xo-reader/src/reader/expect_expr_xs.cpp @@ -229,8 +229,7 @@ namespace xo { expect_expr_xs::on_expr(bp expr, parserstatemachine * p_psm) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); log && log(xtag("exstype", this->exs_type_), xtag("expr", expr.promote())); @@ -244,8 +243,7 @@ namespace xo { expect_expr_xs::on_expr_with_semicolon(bp expr, parserstatemachine * p_psm) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); log && log(xtag("exstype", this->exs_type_), xtag("expr", expr.promote())); diff --git a/xo-reader/src/reader/exprstate.cpp b/xo-reader/src/reader/exprstate.cpp index 07612db4..e4311bd8 100644 --- a/xo-reader/src/reader/exprstate.cpp +++ b/xo-reader/src/reader/exprstate.cpp @@ -109,8 +109,7 @@ namespace xo { { /* returning type description to something that wants it */ - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); log && log(xtag("exstype", p_psm->top_exprstate().exs_type())); @@ -171,8 +170,7 @@ namespace xo { exprstate::on_colon_token(const token_type & tk, parserstatemachine * p_psm) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); constexpr const char * c_self_name = "exprstate::on_colon"; const char * exp = get_expect_str(); diff --git a/xo-reader/src/reader/exprstatestack.cpp b/xo-reader/src/reader/exprstatestack.cpp index effff3a9..4ccad6d6 100644 --- a/xo-reader/src/reader/exprstatestack.cpp +++ b/xo-reader/src/reader/exprstatestack.cpp @@ -4,6 +4,7 @@ */ #include "exprstatestack.hpp" +#include "pretty_exprstatestack.hpp" #include namespace xo { diff --git a/xo-reader/src/reader/lambda_xs.cpp b/xo-reader/src/reader/lambda_xs.cpp index e8111e66..61144b54 100644 --- a/xo-reader/src/reader/lambda_xs.cpp +++ b/xo-reader/src/reader/lambda_xs.cpp @@ -5,6 +5,7 @@ #include "exprstatestack.hpp" #include "expect_formal_arglist_xs.hpp" #include "expect_expr_xs.hpp" +#include "expect_type_xs.hpp" #include "xo/expression/Lambda.hpp" namespace xo { @@ -20,6 +21,8 @@ namespace xo { case lambdastatetype::lm_1: return "lm_1"; case lambdastatetype::lm_2: return "lm_2"; case lambdastatetype::lm_3: return "lm_3"; + case lambdastatetype::lm_4: return "lm_4"; + case lambdastatetype::lm_5: return "lm_5"; default: break; } @@ -47,9 +50,11 @@ namespace xo { lambda_xs::get_expect_str() const { /* - * lambda (x : f64) { ... } ; - * ^ ^ ^ ^ - * | | | lm_3 + * lambda (x : f64) : f64 { ... } ; + * ^ ^ ^ ^ ^ ^ + * | | | | | lm_5 + * | | | | lm_4:expect_expression + * | | | lm_3 * | | lm_2 * | lm_1: * expect_expression @@ -63,8 +68,12 @@ namespace xo { case lambdastatetype::lm_1: return "lambda-params"; case lambdastatetype::lm_2: - return "lambda-body"; + return "colon|lambda-body"; case lambdastatetype::lm_3: + return "type"; + case lambdastatetype::lm_4: + return "lambda-body"; + case lambdastatetype::lm_5: return "semicolon"; } @@ -94,24 +103,12 @@ namespace xo { p_psm->push_envframe(local_env_); - expect_expr_xs::start(p_psm); + //expect_expr_xs::start(p_psm); } else { exprstate::on_formal_arglist(argl, p_psm); } } - void - lambda_xs::on_expr(bp expr, - parserstatemachine * p_psm) - { - if (lmxs_type_ == lambdastatetype::lm_2) { - this->lmxs_type_ = lambdastatetype::lm_3; - this->body_ = expr.promote(); - } else { - exprstate::on_expr(expr, p_psm); - } - } - void lambda_xs::on_expr_with_semicolon(bp expr, parserstatemachine * p_psm) @@ -120,11 +117,74 @@ namespace xo { this->on_semicolon_token(token_type::semicolon(), p_psm); } + void + lambda_xs::on_colon_token(const token_type & tk, + parserstatemachine * p_psm) + { + constexpr const char * c_self_name = "lambda_xs::on_colon_token"; + + if (lmxs_type_ == lambdastatetype::lm_2) { + this->lmxs_type_ = lambdastatetype::lm_3; + expect_type_xs::start(p_psm); + /* control reenters via .on_typedescr() */ + } else { + this->illegal_input_on_token(c_self_name, tk, this->get_expect_str(), p_psm); + } + } + + void + lambda_xs::on_leftbrace_token(const token_type & tk, + parserstatemachine * p_psm) + { + constexpr const char * c_self_name = "lambda_xs::on_leftbrace_token"; + + if (lmxs_type_ == lambdastatetype::lm_2) + this->lmxs_type_ = lambdastatetype::lm_4; + + if (lmxs_type_ == lambdastatetype::lm_4) { + expect_expr_xs::start(p_psm); + /* want { to start expr sequence, that finishes on matching } */ + p_psm->on_leftbrace_token(token_type::leftbrace()); + } else { + this->illegal_input_on_token(c_self_name, tk, this->get_expect_str(), p_psm); + } + } + + void + lambda_xs::on_typedescr(TypeDescr td, + parserstatemachine * p_psm) + { + constexpr const char * c_self_name = "lambda_xs::on_typedescr"; + + if (lmxs_type_ == lambdastatetype::lm_3) { + this->lmxs_type_ = lambdastatetype::lm_4; + this->explicit_return_td_ = td; + expect_expr_xs::start(p_psm); + /* control reenters via .on_expr() or .on_expr_with_semicolon() */ + } else { + this->illegal_input_on_type(c_self_name, td, this->get_expect_str(), p_psm); + } + } + + void + lambda_xs::on_expr(bp expr, + parserstatemachine * p_psm) + { + constexpr const char * c_self_name = "lambda_xs::on_expr"; + + if (lmxs_type_ == lambdastatetype::lm_4) { + this->lmxs_type_ = lambdastatetype::lm_5; + this->body_ = expr.promote(); + } else { + this->illegal_input_on_expr(c_self_name, expr, this->get_expect_str(), p_psm); + } + } + void lambda_xs::on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) { - if (lmxs_type_ == lambdastatetype::lm_3) { + if (lmxs_type_ == lambdastatetype::lm_5) { /* done! */ std::unique_ptr self = p_psm->pop_exprstate(); @@ -134,7 +194,7 @@ namespace xo { /* top env frame recorded arguments to this lambda */ p_psm->pop_envframe(); - rp lm = Lambda::make_from_env(name, local_env_, body_); + rp lm = Lambda::make_from_env(name, local_env_, explicit_return_td_, body_); p_psm->top_exprstate().on_expr(lm, p_psm); p_psm->top_exprstate().on_semicolon_token(tk, p_psm); diff --git a/xo-reader/src/reader/let1_xs.cpp b/xo-reader/src/reader/let1_xs.cpp index 99cae693..1a2c444e 100644 --- a/xo-reader/src/reader/let1_xs.cpp +++ b/xo-reader/src/reader/let1_xs.cpp @@ -141,6 +141,7 @@ namespace xo { rp lambda = Lambda::make_from_env(lambda_name, local_env_, + nullptr /*explicit_return_td*/, expr); rp result diff --git a/xo-reader/src/reader/pretty_parserstatemachine.cpp b/xo-reader/src/reader/pretty_parserstatemachine.cpp index 693c1983..3e03b136 100644 --- a/xo-reader/src/reader/pretty_parserstatemachine.cpp +++ b/xo-reader/src/reader/pretty_parserstatemachine.cpp @@ -36,5 +36,19 @@ namespace xo { } } + bool + ppdetail::print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine * x) + { + if (x) { + return ppdetail::print_pretty(ppii, *x); + } else { + if (ppii.upto()) { + return ppii.pps()->print_upto(""); + } else { + ppii.pps()->write(""); + return false; + } + } + } } /*namespace print*/ } /*namespace xo*/ diff --git a/xo-reader/src/reader/sequence_xs.cpp b/xo-reader/src/reader/sequence_xs.cpp index 365b9747..0d5b51ab 100644 --- a/xo-reader/src/reader/sequence_xs.cpp +++ b/xo-reader/src/reader/sequence_xs.cpp @@ -36,8 +36,7 @@ namespace xo { sequence_xs::on_expr(bp expr, parserstatemachine * p_psm) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(p_psm->debug_flag())); log && log(xtag("expr", expr.promote())); @@ -93,6 +92,14 @@ namespace xo { } } + void + sequence_xs::on_expr_with_semicolon(bp expr, + parserstatemachine * p_psm) + { + /* sequence continues until right brace */ + this->on_expr(expr, p_psm); + } + void sequence_xs::on_rightbrace_token(const token_type & /*tk*/, parserstatemachine * p_psm) @@ -107,6 +114,11 @@ namespace xo { p_psm->top_exprstate().on_expr(expr, p_psm); } + void + sequence_xs::print(std::ostream & os) const { + os << ""; + } + } /*namespace scm*/ } /*namespace xo*/ diff --git a/xo-reflect/include/xo/reflect/TypeDescr.hpp b/xo-reflect/include/xo/reflect/TypeDescr.hpp index 53247ad0..57deb81d 100644 --- a/xo-reflect/include/xo/reflect/TypeDescr.hpp +++ b/xo-reflect/include/xo/reflect/TypeDescr.hpp @@ -518,11 +518,21 @@ namespace xo { }; /*TypeDescrBase*/ inline std::ostream & - operator<<(std::ostream & os, TypeDescrBase const & x) { + operator<<(std::ostream & os, const TypeDescrBase & x) { x.display(os); return os; } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, const TypeDescrBase * p) { + if (p) + p->display(os); + else + os << ""; + return os; + } + + /* tag to drive overload resolution */ struct reflected_types_printer {};