xo-expression: add explicit types to all Expressions

This commit is contained in:
Roland Conybeare 2024-06-18 16:55:46 -04:00
commit 9ff173f68a
12 changed files with 223 additions and 54 deletions

View file

@ -20,11 +20,13 @@ namespace xo {
**/
class Apply : public Expression {
public:
using TypeDescr = xo::reflect::TypeDescr;
public:
/** create new apply-expression instance
**/
static ref::rp<Apply> make(const ref::rp<Expression> & fn,
const std::vector<ref::rp<Expression>> & argv)
{
return new Apply(fn, argv);
}
const std::vector<ref::rp<Expression>> & argv);
/** downcast from Expression **/
static ref::brw<Apply> from(ref::brw<Expression> x) {
@ -37,9 +39,11 @@ namespace xo {
virtual void display(std::ostream & os) const;
private:
Apply(const ref::rp<Expression> & fn,
Apply(TypeDescr apply_valuetype,
const ref::rp<Expression> & fn,
const std::vector<ref::rp<Expression>> & argv)
: Expression(exprtype::apply), fn_{fn}, argv_(argv)
: Expression(exprtype::apply, apply_valuetype),
fn_{fn}, argv_(argv)
{}
private:

View file

@ -29,13 +29,12 @@ namespace xo {
using TypeDescr = xo::reflect::TypeDescr;
public:
explicit Constant(const T & x)
: ConstantInterface(exprtype::constant),
value_td_{Reflect::require<T>()},
value_(x)
{
static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>);
}
/** create constant expression representing literal value x **/
static ref::rp<Constant> make(const T & x) {
TypeDescr x_valuetype = Reflect::require<T>();
return new Constant(x_valuetype, x);
}
const T & value() const { return value_; }
@ -59,6 +58,15 @@ namespace xo {
<< ">";
}
private:
explicit Constant(TypeDescr x_type, const T & x)
: ConstantInterface(exprtype::constant, x_type),
value_td_{Reflect::require<T>()},
value_(x)
{
static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>);
}
private:
/** type description for T **/
TypeDescr value_td_;
@ -69,7 +77,7 @@ namespace xo {
template <typename T>
ref::rp<Constant<std::remove_reference_t<T>>>
make_constant(const T & x) {
return new Constant(x);
return Constant<T>::make(x);
}
} /*namespace ast*/

View file

@ -22,7 +22,7 @@ namespace xo {
public:
/** @p extype sets expression-type; could be constant|primitive **/
ConstantInterface(exprtype extype) : Expression{extype} {}
ConstantInterface(exprtype extype, TypeDescr valuetype) : Expression{extype, valuetype} {}
/** downcast from Expression **/
static ref::brw<ConstantInterface> from(ref::brw<Expression> x) {

View file

@ -5,6 +5,7 @@
#pragma once
#include "xo/reflect/TypeDescr.hpp"
#include "xo/refcnt/Refcounted.hpp"
#include "exprtype.hpp"
@ -23,12 +24,19 @@ namespace xo {
*
* Expressions are immutable. This means they can resused
* across jit interactions
*
* Every expression evaluates to a value with a particular type
**/
class Expression : public ref::Refcount {
public:
explicit Expression(exprtype extype) : extype_{extype} {}
using TypeDescr = xo::reflect::TypeDescr;
public:
explicit Expression(exprtype extype, TypeDescr valuetype)
: extype_{extype}, valuetype_{valuetype}{}
exprtype extype() const { return extype_; }
TypeDescr valuetype() const { return valuetype_; }
/** write human-readable representation to stream **/
virtual void display(std::ostream & os) const = 0;
@ -38,6 +46,10 @@ namespace xo {
private:
/** expression type (constant | apply | ..) for this expression **/
exprtype extype_ = exprtype::invalid;
/** type information (when available) for values produced by this
* expression.
**/
TypeDescr valuetype_ = nullptr;
}; /*Expression*/
inline std::ostream &

View file

@ -18,17 +18,16 @@ namespace xo {
**/
class IfExpr : public Expression {
public:
/** @p test test-expression; always execute
* @p when_true then-branch; executes only when test succeeds
* @p when_false else-branch; executes only when test fails
using TypeDescr = xo::reflect::TypeDescr;
public:
/** create expression for conditional execution of
* @p when_true or @p when_false, depending on result
* of evaluating expression @p test
**/
IfExpr(const ref::rp<Expression> & test,
const ref::rp<Expression> & when_true,
const ref::rp<Expression> & when_false)
: Expression(exprtype::ifexpr),
test_{test},
when_true_{when_true},
when_false_{when_false} {}
static ref::rp<IfExpr> make(const ref::rp<Expression> & test,
const ref::rp<Expression> & when_true,
const ref::rp<Expression> & when_false);
/** downcast from Expression **/
static ref::brw<IfExpr> from(ref::brw<Expression> x) {
@ -43,6 +42,24 @@ namespace xo {
virtual void display(std::ostream & os) const override;
private:
/**
* @p ifexpr_type type for value produced by if-expression.
* same as both when_true->valuetype() and
* when_false->valuetype().
* @p test test-expression; always execute
* @p when_true then-branch; executes only when test succeeds
* @p when_false else-branch; executes only when test fails
**/
IfExpr(TypeDescr ifexpr_type,
const ref::rp<Expression> & test,
const ref::rp<Expression> & when_true,
const ref::rp<Expression> & when_false)
: Expression(exprtype::ifexpr, ifexpr_type),
test_{test},
when_true_{when_true},
when_false_{when_false} {}
private:
/** if:
* (if x y z)
@ -59,7 +76,7 @@ namespace xo {
const ref::rp<Expression> & when_true,
const ref::rp<Expression> & when_false)
{
return new IfExpr(test, when_true, when_false);
return IfExpr::make(test, when_true, when_false);
}
} /*namespace ast*/
} /*namespace xo*/

View file

@ -18,12 +18,14 @@ namespace xo {
**/
class Lambda : public Expression {
public:
/** @p argv Formal parameters, in left-to-right order
/**
* @p name Name for this lambda -- must be unique
* @p argv Formal parameters, in left-to-right order
* @p body Expression for body of this function
**/
Lambda(const std::string & name,
const std::vector<std::string> & argv,
const ref::rp<Expression> & body);
static ref::rp<Lambda> make(const std::string & name,
const std::vector<ref::rp<Expression>> & argv,
const ref::rp<Expression> & body);
/** downcast from Expression **/
static ref::brw<Lambda> from(ref::brw<Expression> x) {
@ -32,7 +34,7 @@ namespace xo {
const std::string & name() const { return name_; }
const std::string & type_str() const { return type_str_; }
const std::vector<std::string> & argv() const { return argv_; }
const std::vector<ref::rp<Expression>> & argv() const { return argv_; }
const ref::rp<Expression> & body() const { return body_; }
/** return number of arguments expected by this function **/
@ -42,6 +44,15 @@ namespace xo {
virtual void display(std::ostream & os) const override;
private:
/** @param lambda_type. function type for this lambda.
* We arbitrarily choose the form "Retval(*)(Args...)"
**/
Lambda(const std::string & name,
TypeDescr lambda_type,
const std::vector<ref::rp<Expression>> & argv,
const ref::rp<Expression> & body);
private:
/** lambda name. Initially supporting only form like
* (define (foo x y z)
@ -56,17 +67,17 @@ namespace xo {
**/
std::string type_str_;
/** formal argument names **/
std::vector<std::string> argv_;
std::vector<ref::rp<Expression>> argv_;
/** function body **/
ref::rp<Expression> body_;
}; /*Lambda*/
inline ref::rp<Lambda>
make_lambda(const std::string & name,
const std::vector<std::string> & argv,
const std::vector<ref::rp<Expression>> & argv,
const ref::rp<Expression> & body)
{
return new Lambda(name, argv, body);
return Lambda::make(name, argv, body);
}
} /*namespace ast*/
} /*namespace xo*/

View file

@ -20,6 +20,10 @@ namespace xo {
*
* In any case, a primitive serves as both declaration and definition
* (May be possible to relax this to declaration-only using null value_ as sentinel..?)
*
* @tparam FunctionPointer a function-pointer type, e.g. double(*)(double).
* Must be in this "canonical form". std::function<double(double)>
* won't work here.
**/
template <typename FunctionPointer>
class Primitive: public PrimitiveInterface {
@ -29,18 +33,12 @@ namespace xo {
using TypeDescr = xo::reflect::TypeDescr;
public:
Primitive(const std::string & name,
FunctionPointer fnptr)
: PrimitiveInterface(),
name_{name},
value_td_{Reflect::require_function<FunctionPointer>()},
value_{fnptr}
{
if (!value_td_->is_function())
throw std::runtime_error("Primitive: expected function pointer");
if (!value_td_->fn_retval())
throw std::runtime_error("Primitive: expected non-null function return value");
}
static ref::rp<Primitive> make(const std::string & name,
FunctionPointer fnptr) {
TypeDescr fn_type = Reflect::require<FunctionPointer>();
return new Primitive(fn_type, name, fnptr);
}
FunctionPointer value() const { return value_; }
@ -69,6 +67,22 @@ namespace xo {
<< ">";
}
private:
Primitive(TypeDescr fn_type,
const std::string & name,
FunctionPointer fnptr)
: PrimitiveInterface(fn_type),
name_{name},
value_td_{Reflect::require_function<FunctionPointer>()},
value_{fnptr}
{
if (!value_td_->is_function())
throw std::runtime_error("Primitive: expected function pointer");
if (!value_td_->fn_retval())
throw std::runtime_error("Primitive: expected non-null function return value");
}
private:
// from Expression:
// exprtype extype_
@ -85,7 +99,7 @@ namespace xo {
template <typename FunctionPointer>
ref::rp<Primitive<FunctionPointer>>
make_primitive(const std::string & name, FunctionPointer x) {
return new Primitive(name, x);
return Primitive<FunctionPointer>::make(name, x);
}
} /*namespace ast*/
} /*namespace xo*/

View file

@ -14,7 +14,8 @@ namespace xo {
namespace ast {
class PrimitiveInterface : public ConstantInterface {
public:
PrimitiveInterface() : ConstantInterface(exprtype::primitive) {}
PrimitiveInterface(TypeDescr fn_type)
: ConstantInterface(exprtype::primitive, fn_type) {}
/** downcast from Expression **/
static ref::brw<PrimitiveInterface> from(ref::brw<Expression> x) {

View file

@ -15,7 +15,14 @@ namespace xo {
**/
class Variable : public Expression {
public:
Variable(const std::string & name) : Expression(exprtype::variable), name_{name} {}
/** create expression representing a variable
* identified by @p name, that can take on values
* described by @p var_type.
**/
static ref::rp<Variable> make(const std::string & name,
TypeDescr var_type) {
return new Variable(name, var_type);
}
/** downcast from Expression **/
static ref::brw<Variable> from(ref::brw<Expression> x) {
@ -26,14 +33,21 @@ namespace xo {
virtual void display(std::ostream & os) const;
private:
Variable(const std::string & name,
TypeDescr var_type)
: Expression(exprtype::variable, var_type),
name_{name} {}
private:
/** variable name **/
std::string name_;
}; /*Variable*/
inline ref::rp<Variable>
make_var(const std::string & name) {
return new Variable(name);
make_var(const std::string & name,
reflect::TypeDescr var_type) {
return Variable::make(name, var_type);
}
} /*namespace ast*/
} /*namespace xo*/

View file

@ -4,7 +4,29 @@
#include "xo/indentlog/print/vector.hpp"
namespace xo {
using xo::ref::rp;
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);
}
void
Apply::display(std::ostream & os) const {
os << "<Apply"

View file

@ -4,7 +4,34 @@
#include "xo/indentlog/print/vector.hpp"
namespace xo {
using xo::ref::rp;
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"

View file

@ -1,16 +1,55 @@
/* @file Lambda.cpp */
#include "Lambda.hpp"
#include "xo/reflect/TypeDescr.hpp"
#include "xo/reflect/function/FunctionTdx.hpp"
#include "xo/indentlog/print/vector.hpp"
namespace xo {
using xo::reflect::TypeDescrBase;
using xo::reflect::FunctionTdxInfo;
using xo::ref::rp;
using std::stringstream;
namespace ast {
rp<Lambda>
Lambda::make(const std::string & name,
const std::vector<rp<Expression>> & argv,
const ref::rp<Expression> & body)
{
using xo::reflect::FunctionTdx;
/** 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 new Lambda(name,
lambda_td,
argv,
body);
} /*make*/
Lambda::Lambda(const std::string & name,
const std::vector<std::string> & argv,
TypeDescr lambda_type,
const std::vector<rp<Expression>> & argv,
const ref::rp<Expression> & body)
: Expression(exprtype::lambda),
: Expression(exprtype::lambda, lambda_type),
name_{name},
argv_{argv},
body_{body}