Add 'xo-expression/' from commit '5ac3c03a0c'

git-subtree-dir: xo-expression
git-subtree-mainline: d0f5ccc1ce
git-subtree-split: 5ac3c03a0c
This commit is contained in:
Roland Conybeare 2025-05-11 01:22:16 -05:00
commit aecabbb144
46 changed files with 3241 additions and 0 deletions

View file

@ -0,0 +1,72 @@
/* @file Apply.cpp */
#include "Apply.hpp"
#include "Primitive.hpp"
#include "xo/indentlog/print/vector.hpp"
namespace xo {
namespace ast {
rp<Apply>
Apply::make(const rp<Expression> & fn,
const std::vector<rp<Expression>> & argv)
{
/* extract result type from function type */
TypeDescr fn_valuetype = fn->valuetype();
if (!fn_valuetype->is_function()) {
throw std::runtime_error
(tostr("Apply::make: found expression F in function position,"
" with value-type FT where a function type expected",
xtag("FT", fn_valuetype->short_name()),
xtag("F", fn_valuetype)));
}
TypeDescr fn_retval_type = fn_valuetype->fn_retval();
return new Apply(fn_retval_type, fn, argv);
}
rp<Apply>
Apply::make_add2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_add2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_sub2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_sub2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_mul2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_mul2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_div2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_div2_f64(),
{lhs, rhs});
}
void
Apply::display(std::ostream & os) const {
os << "<Apply"
<< xtag("fn", fn_)
<< xtag("argv", argv_)
<< ">";
}
} /*namespace ast*/
} /*namespace xo*/
/* end Apply.cpp */

View file

@ -0,0 +1,93 @@
/* file AssignExpr.cpp
*
* author: Roland Conybeare
*/
#include "AssignExpr.hpp"
#include "xo/indentlog/print/tag.hpp"
namespace xo {
namespace ast {
rp<AssignExpr>
AssignExpr::make(const rp<Variable> & lhs,
const rp<Expression> & rhs)
{
return new AssignExpr(lhs, rhs);
}
AssignExpr::AssignExpr(const rp<Variable> & lhs,
const rp<Expression> & rhs)
: Expression(exprtype::assign, rhs->valuetype()),
lhs_{lhs}, rhs_{rhs}
{
this->free_var_set_ = this->calc_free_variables();
}
std::set<std::string>
AssignExpr::calc_free_variables() const
{
std::set<std::string> retval = lhs_->get_free_variables();
std::set<std::string> tmp = rhs_->get_free_variables();
for (const auto & name : tmp)
retval.insert(name);
return retval;
}
std::set<std::string>
AssignExpr::get_free_variables() const {
return free_var_set_;
}
std::size_t
AssignExpr::visit_preorder(VisitFn visitor_fn) {
std::size_t n = 1;
visitor_fn(this);
n += lhs_->visit_preorder(visitor_fn);
n += rhs_->visit_preorder(visitor_fn);
return n;
}
std::size_t
AssignExpr::visit_layer(VisitFn visitor_fn) {
std::size_t n = 1;
visitor_fn(this);
n += lhs_->visit_layer(visitor_fn);
n += rhs_->visit_layer(visitor_fn);
return n;
}
rp<Expression>
AssignExpr::xform_layer(TransformFn xform_fn) {
this->lhs_ = Variable::from(lhs_->xform_layer(xform_fn)).promote();
this->rhs_ = rhs_->xform_layer(xform_fn);
return xform_fn(this);
}
void
AssignExpr::attach_envs(ref::brw<Environment> p) {
lhs_->attach_envs(p);
rhs_->attach_envs(p);
}
void
AssignExpr::display(std::ostream & os) const {
os << "<Assign"
<< xtag("lhs", lhs_)
<< xtag("rhs", rhs_)
<< ">";
}
} /*namespace ast*/
} /*namespace xo*/
/* end AssignExpr.cpp */

View file

@ -0,0 +1,24 @@
# expression/CMakeLists.txt
set(SELF_LIB xo_expression)
set(SELF_SRCS
GeneralizedExpression.cpp
Expression.cpp
DefineExpr.cpp
AssignExpr.cpp
Apply.cpp
Lambda.cpp
Variable.cpp
IfExpr.cpp
Sequence.cpp
LocalEnv.cpp
ConvertExpr.cpp
Primitive.cpp
)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
xo_dependency(${SELF_LIB} reflect)
#xo_dependency(${SELF_LIB} indentlog)
#xo_dependency(${SELF_LIB} subsys)
# end CMakeLists.txt

View file

@ -0,0 +1,53 @@
/* file ConvertExpr.cpp
*
* author: Roland Conybeare
*/
#include "ConvertExpr.hpp"
namespace xo {
namespace ast {
rp<ConvertExpr>
ConvertExpr::make(TypeDescr dest_type,
rp<Expression> arg)
{
return new ConvertExpr(dest_type,
std::move(arg));
}
std::set<std::string>
ConvertExpr::get_free_variables() const {
if (this->arg_)
return this->arg_->get_free_variables();
else
return std::set<std::string>();
}
void
ConvertExpr::display(std::ostream & os) const {
os << "<Convert"
<< xtag("dest_type", this->valuetype()->short_name())
<< xtag("arg", arg_)
<< ">";
}
// ----- ConvertExprAccess -----
rp<ConvertExprAccess>
ConvertExprAccess::make(TypeDescr dest_type,
rp<Expression> arg)
{
return new ConvertExprAccess(dest_type,
std::move(arg));
}
rp<ConvertExprAccess>
ConvertExprAccess::make_empty() {
return new ConvertExprAccess(nullptr /*dest_type*/,
nullptr /*arg*/);
}
} /*namespace ast*/
} /*namespace xo*/
/* end ConvertExpr.cpp */

View file

@ -0,0 +1,99 @@
/* file DefineExpr.cpp
*
* author: Roland Conybeare
*/
#include "DefineExpr.hpp"
namespace xo {
namespace ast {
rp<DefineExpr>
DefineExpr::make(std::string lhs_name,
rp<Expression> rhs)
{
TypeDescr rhs_valuetype = nullptr;
if (rhs)
rhs_valuetype = rhs->valuetype();
return new DefineExpr(rhs_valuetype,
std::move(lhs_name),
std::move(rhs));
} /*make*/
DefineExpr::DefineExpr(TypeDescr rhs_valuetype,
std::string lhs_name,
rp<Expression> rhs)
: Expression(exprtype::define, rhs_valuetype),
lhs_name_{std::move(lhs_name)},
rhs_{std::move(rhs)}
{
this->free_var_set_ = this->calc_free_variables();
}
std::set<std::string>
DefineExpr::calc_free_variables() const
{
std::set<std::string> retval;
if (rhs_)
retval = rhs_->get_free_variables();
/* but remove this variable */
if (!this->lhs_name().empty())
retval.erase(this->lhs_name());
return retval;
} /*calc_free_variables*/
void
DefineExpr::display(std::ostream & os) const {
os << "<Define"
<< xtag("name", lhs_name_)
<< xtag("rhs", rhs_)
<< ">";
} /*display*/
// ----- DefineExprAccess -----
rp<DefineExprAccess>
DefineExprAccess::make(std::string lhs_name,
rp<Expression> rhs)
{
TypeDescr rhs_valuetype = nullptr;
if (rhs)
rhs_valuetype = rhs->valuetype();
return new DefineExprAccess(rhs_valuetype,
std::move(lhs_name),
std::move(rhs));
}
rp<DefineExprAccess>
DefineExprAccess::make_empty()
{
return new DefineExprAccess(nullptr /*rhs_valuetype*/,
"" /*lhs_name*/,
nullptr /*rhs*/);
}
void
DefineExprAccess::assign_rhs(const rp<Expression> & x)
{
assert(x);
this->rhs_ = x;
if (x) {
this->assign_valuetype(x->valuetype());
}
this->free_var_set_ = this->calc_free_variables();
}
} /*namespace ast*/
} /*namespace xo*/
/* end DefineExpr.cpp */

View file

@ -0,0 +1,11 @@
/* @file Expression.cpp */
#include "Expression.hpp"
namespace xo {
namespace ast {
} /*namespace ast*/
} /*namespace xo*/
/* end Expression.cpp */

View file

@ -0,0 +1,14 @@
/* @file GeneralizedExpression.cpp */
#include "GeneralizedExpression.hpp"
namespace xo {
namespace ast {
std::string
GeneralizedExpression::display_string() const {
return tostr(*this);
}
} /*namespace ast*/
} /*namespace xo*/
/* end GeneralizedExpression.cpp */

View file

@ -0,0 +1,45 @@
/* @file IfExpr.cpp */
#include "IfExpr.hpp"
#include "xo/indentlog/print/vector.hpp"
namespace xo {
namespace ast {
rp<IfExpr>
IfExpr::make(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & when_false)
{
/** TODO: verify test returns _boolean_ type **/
if (when_true->valuetype() != when_false->valuetype()) {
throw std::runtime_error
(tostr("IfExpr::make:"
" types {T1,T2} found for branches of if-expr"
" where equal types expected",
xtag("T1", when_true->valuetype()->canonical_name()),
xtag("T2", when_false->valuetype()->canonical_name())));
}
/* arbitrary choice here */
auto ifexpr_type = when_true->valuetype();
return new IfExpr(ifexpr_type,
test,
when_true,
when_false);
} /*make*/
void
IfExpr::display(std::ostream & os) const {
os << "<IfExpr"
<< xtag("test", test_)
<< xtag("when_true", when_true_)
<< xtag("when_false", when_false_)
<< ">";
} /*display*/
} /*namespace ast*/
} /*namespace xo*/
/* end IfExpr.cpp */

View file

@ -0,0 +1,361 @@
/* @file Lambda.cpp */
#include "Lambda.hpp"
#include "xo/reflect/TypeDescr.hpp"
#include "xo/reflect/function/FunctionTdx.hpp"
#include "xo/indentlog/print/vector.hpp"
#include <map>
#include <sstream>
namespace xo {
using xo::reflect::TypeDescr;
using xo::reflect::TypeDescrBase;
using xo::reflect::FunctionTdxInfo;
using std::stringstream;
namespace ast {
TypeDescr
Lambda::assemble_lambda_td(const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
if (!body)
return nullptr;
/** assemble function type.
*
* NOTE: need this to be unique!
**/
std::vector<TypeDescr> arg_td_v;
{
arg_td_v.reserve(argv.size());
for (const auto & arg : argv) {
arg_td_v.push_back(arg->valuetype());
}
}
auto function_info
= FunctionTdxInfo(body->valuetype(),
arg_td_v,
false /*!is_noexcept*/);
TypeDescr lambda_td
= TypeDescrBase::require_by_fn_info(function_info);
return lambda_td;
}
std::string
Lambda::assemble_type_str(TypeDescr lambda_td) {
assert(lambda_td);
std::stringstream ss;
ss << lambda_td->fn_retval()->short_name()
<< "(";
for (std::size_t i = 0, n = lambda_td->n_fn_arg(); i < n; ++i) {
if (i > 0)
ss << ",";
ss << lambda_td->fn_arg(i)->short_name();
}
ss << ")";
return ss.str();
}
rp<Lambda>
Lambda::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
using xo::reflect::FunctionTdx;
rp<LocalEnv> env = LocalEnv::make(argv);
TypeDescr lambda_td = assemble_lambda_td(argv, body);
rp<Lambda> retval
= new Lambda(name,
lambda_td,
env,
body);
/* need two-phase construction b/c pointer cycle */
env->assign_origin(retval.get());
return retval;
} /*make*/
std::set<std::string>
Lambda::calc_free_variables() const
{
std::set<std::string> retval
= body_->get_free_variables();
/* but remove formals. */
for (const auto & var : local_env_->argv())
retval.erase(var->name());
return retval;
} /*calc_free_variables*/
std::map<std::string, rp<Variable>>
Lambda::regularize_layer_vars()
{
/* regularize local_env+body: make sure exactly one instance
* (i.e. with object identity) of a Variable appears
* within one layer of a lambda body.
*
* Here 'layer' means excluding appearance in any nested lambdas
* (i.e. whether or not such appearance would resolve to the same
* memory location).
*
* Motivation is to unify Variables that would use the same
* binding_path to resolve their runtime location.
*/
std::map<std::string, rp<Variable>> var_map;
for (const auto & arg : local_env_->argv()) {
/* each arg name can appear at most once
* in a particular lambda's parameter list
*/
assert(var_map.find(arg->name()) == var_map.end());
var_map[arg->name()] = arg;
}
this->body_
= (body_->xform_layer
([&var_map](ref::brw<Expression> x) -> rp<Expression>
{
if (x->extype() == exprtype::variable) {
ref::brw<Variable> var = Variable::from(x);
auto ix = var_map.find(var->name());
if (ix == var_map.end()) {
/* add to var_map, copy to ensure Variable
* not shared with any other layer
*/
var_map[var->name()] = Variable::copy(var);
return var.get();
} else {
/* substitute already-encountered var_map[] member */
return ix->second.get();
}
} else {
return x.get();
}
}));
return var_map;
} /*regularize_layer_vars*/
void
Lambda::complete_assembly_from_body() {
if (body_) {
TypeDescr lambda_td
= assemble_lambda_td(this->local_env_->argv(), body_);
if (lambda_td)
this->type_str_ = assemble_type_str(lambda_td);
this->layer_var_map_ = this->regularize_layer_vars();
this->free_var_set_ = this->calc_free_variables();
std::map<std::string, ref::brw<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(ref::brw<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
ref::brw<Lambda> lm = Lambda::from(expr);
nested_lambda_map[lm->name()] = lm.get();
}
});
}
this->nested_lambda_map_ = std::move(nested_lambda_map);
/* establish the set of captured local vars.
* These are any formal parameters that appear free in
* any layer of a nested lambda.
*/
std::set<std::string> captured_var_set;
{
for (const auto & ix : nested_lambda_map_) {
std::set<std::string> nested_free_var_set
= ix.second->get_free_variables();
for (const auto & jx : nested_free_var_set) {
/* check whether variable *jx is one of this lambda's
* formals
*/
auto bind = this->local_env_->lookup_local_binding(jx);
if (bind.i_link_ == 0) {
/* yup, it's a formal parameter of this lambda */
captured_var_set.insert(jx);
}
}
}
}
this->captured_var_set_ = std::move(captured_var_set);
/* in particular:
* - establish binding path (intrusively) for each variable
* assigns Variable::path_
*/
this->body_->attach_envs(local_env_);
}
}
Lambda::Lambda(const std::string & name,
TypeDescr lambda_td,
const rp<LocalEnv> & local_env,
const rp<Expression> & body)
: FunctionInterface(exprtype::lambda, lambda_td),
name_{name},
body_{body},
local_env_{local_env}
{
#ifdef OBSOLETE
stringstream ss;
ss << "double";
ss << "(";
for (std::size_t i = 0, n = this->n_arg(); i < n; ++i) {
if (i > 0)
ss << ",";
ss << "double";
}
ss << ")";
#endif
if (lambda_td)
this->type_str_ = assemble_type_str(lambda_td);
/* ensure variables are unique within layer for this lambda */
this->layer_var_map_ = this->regularize_layer_vars();
this->free_var_set_ = this->calc_free_variables();
std::map<std::string, ref::brw<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(ref::brw<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
ref::brw<Lambda> lm = Lambda::from(expr);
nested_lambda_map[lm->name()] = lm.get();
}
});
}
this->nested_lambda_map_ = std::move(nested_lambda_map);
/* establish the set of captured local vars.
* These are any formal parameters that appear free in
* any layer of a nested lambda.
*/
std::set<std::string> captured_var_set;
{
for (const auto & ix : nested_lambda_map_) {
std::set<std::string> nested_free_var_set
= ix.second->get_free_variables();
for (const auto & jx : nested_free_var_set) {
/* check whether variable *jx is one of this lambda's
* formals
*/
auto bind = this->local_env_->lookup_local_binding(jx);
if (bind.i_link_ == 0) {
/* yup, it's a formal parameter of this lambda */
captured_var_set.insert(jx);
}
}
}
}
this->captured_var_set_ = std::move(captured_var_set);
/* in particular:
* - establish binding path (intrusively) for each variable
* assigns Variable::path_
*/
this->body_->attach_envs(local_env_);
} /*ctor*/
void
Lambda::attach_envs(ref::brw<Environment> p) {
local_env_->assign_parent(p);
/** establish a binding path for each variable **/
}
void
Lambda::display(std::ostream & os) const {
os << "<Lambda"
<< xtag("name", name_)
<< xtag("argv", local_env_->argv())
<< xtag("body", body_)
<< ">";
} /*display*/
// ----- Lambda Access -----
rp<LambdaAccess>
LambdaAccess::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
TypeDescr lambda_td = assemble_lambda_td(argv, body);
rp<LocalEnv> env = LocalEnv::make(argv);
rp<LambdaAccess> retval
= new LambdaAccess(name,
lambda_td,
env,
body);
/* need two-phase construction b/c pointer cycle */
env->assign_origin(retval.get());
return retval;
}
rp<LambdaAccess>
LambdaAccess::make_empty()
{
return new LambdaAccess("" /*name*/,
nullptr /*lambda_td*/,
nullptr /*local_env*/,
nullptr /*body*/);
}
LambdaAccess::LambdaAccess(const std::string & name,
TypeDescr lambda_td,
const rp<LocalEnv> & local_env,
const rp<Expression> & body)
: Lambda(name, lambda_td, local_env, body)
{}
void
LambdaAccess::assign_body(const rp<Expression> & body) {
this->body_ = body;
this->complete_assembly_from_body();
}
} /*namespace ast*/
} /*namespace xo*/
/* end Lambda.cpp */

View file

@ -0,0 +1,44 @@
/* file LocalEnv.cpp
*
* author: Roland Conybeare
*/
#include "LocalEnv.hpp"
namespace xo {
namespace ast {
binding_path
LocalEnv::lookup_local_binding(const std::string & vname) const
{
int j_slot = 0;
for (const auto & arg : argv_) {
if (arg->name() == vname)
return { 0 /*i_link*/, j_slot };
++j_slot;
}
return { -2 /*i_link: sentinel*/, 0 };
} /*lookup_local_binding*/
binding_path
LocalEnv::lookup_binding(const std::string & vname) const
{
{
auto local = this->lookup_local_binding(vname);
if (local.i_link_ == 0)
return local;
}
auto free = parent_env_->lookup_binding(vname);
if (free.i_link_ == -1)
return free;
else
return { free.i_link_ + 1, free.j_slot_ };
} /*lookup_binding*/
} /*namespace ast*/
} /*namespace xo*/
/* end LocalEnv.cpp */

View file

@ -0,0 +1,89 @@
/* @file Primitive.cpp */
#include "Primitive.hpp"
extern "C" {
double
add2_f64(double x, double y) {
return x + y;
}
double
sub2_f64(double x, double y) {
return x - y;
}
double
mul2_f64(double x, double y) {
return x * y;
}
double
div2_f64(double x, double y) {
return x / y;
}
}
namespace xo {
namespace ast {
auto
Primitive_f64::make_add2_f64() -> rp<PrimitiveType>
{
static rp<PrimitiveType> s_retval;
if (!s_retval)
s_retval = Primitive::make("add2_f64",
&add2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_add);
return s_retval;
}
auto
Primitive_f64::make_sub2_f64() -> rp<PrimitiveType>
{
static rp<PrimitiveType> s_retval;
if (!s_retval)
s_retval = Primitive::make("sub2_f64",
&sub2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_sub);
return s_retval;
}
auto
Primitive_f64::make_mul2_f64() -> rp<PrimitiveType>
{
static rp<PrimitiveType> s_retval;
if (!s_retval)
s_retval = Primitive::make("mul2_f64",
&mul2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_mul);
return s_retval;
}
auto
Primitive_f64::make_div2_f64() -> rp<PrimitiveType>
{
static rp<PrimitiveType> s_retval;
if (!s_retval)
s_retval = Primitive::make("div2_f64",
&div2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_div);
return s_retval;
}
} /*namespace scm*/
} /*namespace xo*/
/* end Primitive.cpp */

View file

@ -0,0 +1,78 @@
/* @file Sequence.cpp */
#include "Sequence.hpp"
#include <cstddef>
namespace xo {
namespace ast {
std::set<std::string>
Sequence::get_free_variables() const {
std::set<std::string> retval;
for (const auto & x : expr_v_) {
std::set<std::string> free_vars;
free_vars = x->get_free_variables();
for (const auto & y : free_vars)
retval.insert(y);
}
return retval;
}
std::size_t
Sequence::visit_preorder(VisitFn visitor_fn) {
std::size_t n = 1;
visitor_fn(this);
for (const auto & x : expr_v_)
n += x->visit_preorder(visitor_fn);
return n;
}
std::size_t
Sequence::visit_layer(VisitFn visitor_fn) {
std::size_t n = 1;
visitor_fn(this);
for (const auto & x : expr_v_)
n += x->visit_layer(visitor_fn);
return n;
}
rp<Expression>
Sequence::xform_layer(TransformFn xform_fn) {
for (std::size_t i = 0, n = expr_v_.size(); i < n; ++i) {
expr_v_[i] = expr_v_[i]->xform_layer(xform_fn);
}
return xform_fn(this);
}
void
Sequence::attach_envs(ref::brw<Environment> p) {
for (const auto & x : expr_v_)
x->attach_envs(p);
}
void
Sequence::display(std::ostream & os) const {
os << "<Sequence";
std::size_t i = 0;
for (const auto & x : expr_v_) {
std::string i_str = tostr("[", i, "]");
os << xtag(i_str.c_str(), x);
}
os << ">";
}
} /*namespace scm*/
} /*namespace xo*/
/* end Sequence.cpp */

View file

@ -0,0 +1,32 @@
/* @file Variable.cpp */
#include "Variable.hpp"
#include "Environment.hpp"
namespace xo {
namespace ast {
void
Variable::attach_envs(ref::brw<Environment> e) {
/** e makes accessible all enclosing lexical scopes **/
if (this->path_.i_link_ == -2 /*sentinel*/) {
this->path_ = e->lookup_binding(this->name_);
} else {
/* have already established binding for this Variable */
}
} /*attach_envs*/
void
Variable::display(std::ostream & os) const {
os << "<Variable"
<< xtag("name", name_);
if (this->valuetype())
os << xtag("type", this->valuetype()->short_name());
else
os << xtag("type", "nullptr");
os << ">";
} /*display*/
} /*namespace ast*/
} /*namespace xo*/
/* end Variable.cpp */

View file

@ -0,0 +1,14 @@
/* @file intrinsics.cpp */
#include "intrinsics.hpp"
/* FIXME: don't know how to mangle symbols yet,
* so putting functions invoked from jit into global namespace
*/
extern "C"
int32_t
mul_i32(int32_t x, int32_t y) {
return x * y;
}
/* end intrinsics.cpp */