xo-expression xo-reader: type unifier + misc improvements
This commit is contained in:
parent
6a7353f689
commit
75b74918b7
31 changed files with 1005 additions and 76 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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@")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ namespace xo {
|
|||
**/
|
||||
static rp<Lambda> make_from_env(const std::string & name,
|
||||
const rp<LocalEnv> & env,
|
||||
TypeDescr explicit_return_td,
|
||||
const rp<Expression> & body);
|
||||
|
||||
/** downcast from Expression **/
|
||||
|
|
@ -105,6 +106,7 @@ namespace xo {
|
|||
* and body expression @p body
|
||||
**/
|
||||
static TypeDescr assemble_lambda_td(const std::vector<rp<Variable>> & argv,
|
||||
TypeDescr explicit_return_td,
|
||||
const rp<Expression> & body);
|
||||
|
||||
/** create string description for function signature,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
/** @file TypeBlueprint.hpp **/
|
||||
|
||||
#include "xo/refcnt/Refcounted.hpp"
|
||||
#include "type_ref.hpp"
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
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<type_var, rp<TypeBlueprint>>;
|
||||
|
||||
/** @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<TypeBlueprint> make(const type_ref& ref);
|
||||
/** contruct blueprint for type variable @p name.
|
||||
* equivalent to @c make(type_ref(name, nullptr))
|
||||
**/
|
||||
static rp<TypeBlueprint> 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<TypeBlueprint> lhs, bp<TypeBlueprint> 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<type_var> * p_typevar_set) const;
|
||||
|
||||
/** apply substitutions in @p sub_map to this type **/
|
||||
bp<TypeBlueprint> 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 **/
|
||||
|
|
@ -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*/
|
||||
|
|
|
|||
60
xo-expression/include/xo/expression/typeinf/type_unifier.hpp
Normal file
60
xo-expression/include/xo/expression/typeinf/type_unifier.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/** @file type_unifier.hpp **/
|
||||
|
||||
#include "type_ref.hpp"
|
||||
#include "TypeBlueprint.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace xo {
|
||||
namespace scm {
|
||||
struct unify_result {
|
||||
/** true iff unification success **/
|
||||
bool success_;
|
||||
/** blueprint (possibly concrete) for unified type **/
|
||||
rp<TypeBlueprint> 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<TypeBlueprint> t1,
|
||||
bp<TypeBlueprint> t2,
|
||||
bp<TypeBlueprint> s1,
|
||||
bp<TypeBlueprint> 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<TypeBlueprint> lhs, bp<TypeBlueprint> rhs);
|
||||
|
||||
private:
|
||||
type_substitution_map constraint_map_;
|
||||
};
|
||||
|
||||
} /*namespace scm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/** end type_unifier.hpp **/
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace xo {
|
|||
namespace ast {
|
||||
TypeDescr
|
||||
Lambda::assemble_lambda_td(const std::vector<rp<Variable>> & argv,
|
||||
TypeDescr explicit_return_td,
|
||||
const rp<Expression> & 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>
|
||||
Lambda::make_from_env(const std::string & name,
|
||||
const rp<LocalEnv> & env,
|
||||
TypeDescr explicit_return_td,
|
||||
const rp<Expression> & body)
|
||||
{
|
||||
TypeDescr lambda_td = assemble_lambda_td(env->argv(), body);
|
||||
TypeDescr lambda_td = assemble_lambda_td(env->argv(), explicit_return_td, body);
|
||||
|
||||
rp<Lambda> retval
|
||||
= new Lambda(name,
|
||||
|
|
@ -96,7 +106,9 @@ namespace xo {
|
|||
{
|
||||
rp<LocalEnv> 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<std::string>
|
||||
|
|
@ -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("<Lambda"))
|
||||
return false;
|
||||
if (!pps->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("<Lambda");
|
||||
pps->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<Expression> & body,
|
||||
const rp<Environment> & 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<LocalEnv> env = LocalEnv::make(argv, parent_env);
|
||||
|
||||
rp<LambdaAccess> retval
|
||||
|
|
|
|||
112
xo-expression/src/expression/typeinf/TypeBlueprint.cpp
Normal file
112
xo-expression/src/expression/typeinf/TypeBlueprint.cpp
Normal file
|
|
@ -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>
|
||||
TypeBlueprint::make(const type_ref & ref)
|
||||
{
|
||||
return new TypeBlueprint(ref);
|
||||
}
|
||||
|
||||
rp<TypeBlueprint>
|
||||
TypeBlueprint::typevar(const type_var & name)
|
||||
{
|
||||
return new TypeBlueprint(type_ref(name, nullptr));
|
||||
}
|
||||
|
||||
bool
|
||||
TypeBlueprint::equals(bp<TypeBlueprint> lhs, bp<TypeBlueprint> 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<type_var> * p_typevar_set) const
|
||||
{
|
||||
if (this->is_concrete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: handle structural types
|
||||
|
||||
p_typevar_set->insert(ref_.id());
|
||||
}
|
||||
bp<TypeBlueprint>
|
||||
TypeBlueprint::substitute(const type_substitution_map& sub_map)
|
||||
{
|
||||
bp<TypeBlueprint> 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 << "<TypeBlueprint";
|
||||
os << xtag("id", id());
|
||||
if (td())
|
||||
os << xtag("td", td()->canonical_name());
|
||||
os << ">";
|
||||
}
|
||||
|
||||
} /*namespace scm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/** end TypeBlueprint.cpp **/
|
||||
48
xo-expression/src/expression/typeinf/type_ref.cpp
Normal file
48
xo-expression/src/expression/typeinf/type_ref.cpp
Normal file
|
|
@ -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<int>(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 **/
|
||||
218
xo-expression/src/expression/typeinf/type_unifier.cpp
Normal file
218
xo-expression/src/expression/typeinf/type_unifier.cpp
Normal file
|
|
@ -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 << "<unify_result"
|
||||
<< xtag("success", x.success_)
|
||||
<< xtag("unified", x.unified_);
|
||||
if (x.error_src_function_)
|
||||
os << xtag("error_src_function", x.error_src_function_);
|
||||
if (!x.error_description_.empty())
|
||||
os << xtag("error_description", x.error_description_);
|
||||
os << ">";
|
||||
return os;
|
||||
}
|
||||
|
||||
unify_result
|
||||
type_unifier::occurs_error(const char * src_function,
|
||||
bp<TypeBlueprint> t1,
|
||||
bp<TypeBlueprint> t2,
|
||||
bp<TypeBlueprint> s1,
|
||||
bp<TypeBlueprint> 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<TypeBlueprint> lhs, bp<TypeBlueprint> 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<TypeBlueprint> 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<TypeBlueprint> 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<type_var> 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<type_var> 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 **/
|
||||
10
xo-expression/utest/CMakeLists.txt
Normal file
10
xo-expression/utest/CMakeLists.txt
Normal file
|
|
@ -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)
|
||||
6
xo-expression/utest/expression_utest_main.cpp
Normal file
6
xo-expression/utest/expression_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* file expression_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end expression_utest_main.cpp */
|
||||
239
xo-expression/utest/type_unifier.test.cpp
Normal file
239
xo-expression/utest/type_unifier.test.cpp
Normal file
|
|
@ -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 <catch2/catch.hpp>
|
||||
|
||||
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<rp<TypeBlueprint>()> lhs_;
|
||||
std::function<rp<TypeBlueprint>()> 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<testcase_ufy>
|
||||
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<std::int64_t>())); },
|
||||
.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<std::int64_t>())); },
|
||||
.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<bool>())); },
|
||||
.rhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("b"),
|
||||
Reflect::require<bool>())); },
|
||||
.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<bool>())); },
|
||||
.rhs_ = [](){ return TypeBlueprint::make(type_ref(type_var::from_chars("b"),
|
||||
Reflect::require<char>())); },
|
||||
.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*/
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<TypeTemplate> lhs, bp<TypeTemplate> rhs);
|
||||
|
||||
TypeTemplateRef ref_;
|
||||
type_ref ref_;
|
||||
|
||||
// additional descriptive info...
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 &
|
||||
|
|
|
|||
|
|
@ -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<rp<Variable>> & argl,
|
||||
parserstatemachine * p_psm) override;
|
||||
virtual void on_expr(bp<Expression> expr,
|
||||
parserstatemachine * p_psm) override;
|
||||
virtual void on_expr_with_semicolon(bp<Expression> 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<LocalEnv> local_env_;
|
||||
|
||||
/** explicit return type (if supplied) **/
|
||||
TypeDescr explicit_return_td_ = nullptr;
|
||||
|
||||
/** body expression **/
|
||||
rp<Expression> body_;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,6 @@ namespace xo {
|
|||
return p->pretty_print(ppii);
|
||||
}
|
||||
};
|
||||
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
|
|
|||
|
|
@ -14,5 +14,10 @@ namespace xo {
|
|||
struct ppdetail<xo::scm::parserstatemachine> {
|
||||
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine & x);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ppdetail<xo::scm::parserstatemachine*> {
|
||||
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine * x);
|
||||
};
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
|
|
|||
|
|
@ -26,10 +26,14 @@ namespace xo {
|
|||
|
||||
virtual void on_expr(bp<Expression> expr,
|
||||
parserstatemachine * p_psm) override;
|
||||
virtual void on_expr_with_semicolon(bp<Expression> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -229,8 +229,7 @@ namespace xo {
|
|||
expect_expr_xs::on_expr(bp<Expression> 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<Expression> 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()));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
#include "exprstatestack.hpp"
|
||||
#include "pretty_exprstatestack.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
|
|
|
|||
|
|
@ -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<Expression> 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<Expression> 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<Expression> 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<exprstate> self = p_psm->pop_exprstate();
|
||||
|
|
@ -134,7 +194,7 @@ namespace xo {
|
|||
/* top env frame recorded arguments to this lambda */
|
||||
p_psm->pop_envframe();
|
||||
|
||||
rp<Lambda> lm = Lambda::make_from_env(name, local_env_, body_);
|
||||
rp<Lambda> 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);
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ namespace xo {
|
|||
rp<Expression> lambda
|
||||
= Lambda::make_from_env(lambda_name,
|
||||
local_env_,
|
||||
nullptr /*explicit_return_td*/,
|
||||
expr);
|
||||
|
||||
rp<Expression> result
|
||||
|
|
|
|||
|
|
@ -36,5 +36,19 @@ namespace xo {
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ppdetail<xo::scm::parserstatemachine *>::print_pretty(const ppindentinfo & ppii, const xo::scm::parserstatemachine * x)
|
||||
{
|
||||
if (x) {
|
||||
return ppdetail<xo::scm::parserstatemachine>::print_pretty(ppii, *x);
|
||||
} else {
|
||||
if (ppii.upto()) {
|
||||
return ppii.pps()->print_upto("<nullptr>");
|
||||
} else {
|
||||
ppii.pps()->write("<nullptr>");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
|
|
|||
|
|
@ -36,8 +36,7 @@ namespace xo {
|
|||
sequence_xs::on_expr(bp<Expression> 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<Expression> 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 << "<sequence_xs" << xtag("expr_v.size", expr_v_.size()) << ">";
|
||||
}
|
||||
|
||||
} /*namespace scm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 << "<nullptr>";
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
/* tag to drive overload resolution */
|
||||
struct reflected_types_printer {};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue