git subrepo clone (merge) git@github.com:Rconybea/xo-expression.git xo-expression

subrepo:
  subdir:   "xo-expression"
  merged:   "fbc5b619"
upstream:
  origin:   "git@github.com:Rconybea/xo-expression.git"
  branch:   "main"
  commit:   "fbc5b619"
git-subrepo:
  version:  "0.4.9"
  origin:   "???"
  commit:   "???"
This commit is contained in:
Roland Conybeare 2026-06-06 22:09:38 -04:00
commit 043b2d7efc
62 changed files with 5370 additions and 0 deletions

8
xo-expression/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs projectile config
.projectile
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

12
xo-expression/.gitrepo Normal file
View file

@ -0,0 +1,12 @@
; DO NOT EDIT (unless you know what you are doing)
;
; This subdirectory is a git "subrepo", and this file is maintained by the
; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme
;
[subrepo]
remote = git@github.com:Rconybea/xo-expression.git
branch = main
commit = fbc5b6192ee63c91e24601e20f693fac0a8fa039
parent = 2b0859f3390bc47da9a4348a69f0070fa4269b4d
method = merge
cmdver = 0.4.9

View file

@ -0,0 +1,42 @@
# expression/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_expression VERSION 0.1)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only!
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
# must complete definition of expression lib before configuring examples
add_subdirectory(src/expression)
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
# ----------------------------------------------------------------
if (XO_ENABLE_EXAMPLES)
install(TARGETS xo_expression_ex1 DESTINATION bin/xo/example)
endif()
# ----------------------------------------------------------------
# docs targets depend on all the other library/utest targets
#
#add_subdirectory(docs)
# end CMakeLists.txt

9
xo-expression/LESSONS Normal file
View file

@ -0,0 +1,9 @@
* want reflection to work without requiring std::type_info,
so that in xo-expression we can create function types that
C++ compiler has never encountered.
** This means we need the ability to construct the canonical name
for a type without c++ compiler's assistance.
** need lookup using function ingredients (return types + arg types + noexcept)
to intern lambda types

29
xo-expression/LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

63
xo-expression/README.md Normal file
View file

@ -0,0 +1,63 @@
# xo-expression library
A library for representing abstract syntax trees for EGAD (a small expression-based language).
See also
- [github/Rconybea/xo-jit](https://github.com/Rconybea/xo-jit)
EGAD code generation via LLVM
- [github/Rconybea/xo-pyexpression](https://github.com/Rconybea/xo-pyexpression)
build EGAD expressions from a python session
- [github/Rconybea/xo-pyjit](https://github.com/Rconybea/xo-pyjit)
compile + run EGAD expressions from a python session
## Getting Started
### build + install `xo-cmake` dependency
- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one.
### build + install other XO dependencies
```
$ xo-build --clone --configure --build --install xo-indentlog
$ xo-build --clone --configure --build --install xo-refnct
$ xo-build --clone --configure --build --install xo-subsys
$ xo-build --clone --configure --build --install xo-reflect
```
Note: can use `xo-build -n` to dry-run here
### copy `xo-expression` repository locally
```
$ xo-build --clone xo-expression
```
or eqivalently
```
$ git clone git@github.com:Rconybea/xo-expression.git
```
### build + install xo-expression
```
$ xo-build --configure --build --install xo-expression
```
or equivalently:
```
$ PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-expression -B xo-jit/.build
$ cmake --build xo-expression/.build
$ cmake --install xo-expression/.build
```
### build for unit test coverage
```
$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-expression/.build-ccov
$ cmake --build xo-expression/.build-ccov
```
### LSP support
```
$ cd xo-expression
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```

3
xo-expression/TODO Normal file
View file

@ -0,0 +1,3 @@
- In xo-reflect, TypeDescr objects are immortal.
In original context rationale was recording information for already-established C++ types.
Maybe want to revisit that choice when associating TypeDescr objects with expressions?

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,8 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(reflect)
find_dependency(xo_flatstring)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,16 @@
Random notes.
Type inference.
Expressions represent parsed abstract syntax trees.
We use the same data structure to represent syntax trees both before and after
type inference.
We record each Expression's type using a type_ref instance.
A type_ref does two things:
1. gives the expression's type a unique name
2. records expression's concrete type once type infrerence has completed.
Type inference resolves each Expression's type to a concrete TypeDescr.
An Expression tree can't be compiled unless all reachable Expression nodes
are resolved to concrete types, i.e. type inference has completed successfully.

View file

@ -0,0 +1 @@
add_subdirectory(ex1)

View file

@ -0,0 +1,12 @@
# xo-expression/example/ex1/CMakeLists.txt
set(SELF_EXE xo_expression_ex1)
set(SELF_SRCS ex1.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_dependency(${SELF_EXE} xo_expression)
xo_dependency(${SELF_EXE} refcnt)
endif()
# end CMakeLists.txt

View file

@ -0,0 +1,47 @@
/** @file ex1.cpp **/
#include "xo/expression/Constant.hpp"
#include "xo/expression/PrimitiveExpr.hpp"
#include "xo/expression/llvmintrinsic.hpp"
#include <iostream>
#include <cmath>
#include <math.h>
// address of &sqrt ambiguous on osx/clang
// (perhaps it's a template..?)
//
double xo_sqrt(double x) {
return sqrt(x);
}
int
main() {
using xo::scm::make_constant;
using xo::scm::make_primitive;
using xo::scm::llvmintrinsic;
using std::cout;
using std::endl;
{
auto expr = make_constant(7);
}
{
auto expr = make_primitive("sqrt",
&xo_sqrt,
false /*!explicit_symbol_def*/,
llvmintrinsic::fp_sqrt);
auto expr_td = expr->value_td();
cout << "expr_td: " << expr_td->short_name() << endl;
cout << "expr_td->is_function(): " << expr_td->is_function() << endl;
cout << "expr_td->fn_retval(): " << expr_td->fn_retval()->short_name() << endl;
cout << "expr_td->n_fn_arg(): " << expr_td->n_fn_arg() << endl;
for (uint32_t i = 0; i < expr_td->n_fn_arg(); ++i)
cout << "expr_td->fn_arg(" << i << "): " << expr_td->fn_arg(i)->short_name() << endl;
cout << "expr_td->fn_is_noexcept(): " << expr_td->fn_is_noexcept() << endl;
}
}
/** end ex1.cpp **/

View file

@ -0,0 +1,220 @@
/** @file Apply.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
namespace xo {
namespace scm {
/** @class Apply
* @brief syntax for a function call.
*
* In general we don't know function to be invoked
* until runtime, depending on the nature of Expression.
**/
class Apply : public Expression {
public:
using TypeDescr = xo::reflect::TypeDescr;
public:
/** create new apply-expression instance
**/
static rp<Apply> make(const rp<Expression> & fn,
const std::vector<rp<Expression>> & argv);
/** create apply-expression to compare two 64-bit integers **/
static rp<Apply> make_cmp_eq_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to compare two 64-bit integers **/
static rp<Apply> make_cmp_ne_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression for less-than comparison of two 64-bit integers **/
static rp<Apply> make_cmp_lt_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression for less-than-or-equal comparison of two 64-bit integers **/
static rp<Apply> make_cmp_le_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression for greater-than comparison of two 64-bit integers **/
static rp<Apply> make_cmp_gt_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression for greater-than-or-equal comparison of two 64-bit integers **/
static rp<Apply> make_cmp_ge_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to add two 64-bit integers **/
static rp<Apply> make_add2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to subtract two 64-bit integers **/
static rp<Apply> make_sub2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to multiply two 64-bit integers **/
static rp<Apply> make_mul2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to divide two 64-bit integers **/
static rp<Apply> make_div2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to add two 64-bit floating-point numbers **/
static rp<Apply> make_add2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to subtract two 64-bit floating-point numbers **/
static rp<Apply> make_sub2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to multiply two 64-bit floating-point numbers **/
static rp<Apply> make_mul2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to divide two 64-bit floating-point numbers **/
static rp<Apply> make_div2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** downcast from Expression **/
static bp<Apply> from(bp<Expression> x) {
return bp<Apply>::from(x);
}
const rp<Expression> & fn() const { return fn_; }
const std::vector<rp<Expression>> & argv() const { return argv_; }
std::size_t n_arg() const { return argv_.size(); }
const rp<Expression> & lookup_arg(size_t i) const { return argv_.at(i); }
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> retval = fn_->get_free_variables();
for (const auto & arg : argv_) {
std::set<std::string> arg_free_set
= arg->get_free_variables();
for (const auto & name : arg_free_set)
retval.insert(name);
}
return retval;
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += fn_->visit_preorder(visitor_fn);
for (const auto & arg : argv_)
n += arg->visit_preorder(visitor_fn);
return n;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += fn_->visit_layer(visitor_fn);
for (const auto & arg : argv_)
n += arg->visit_layer(visitor_fn);
return n;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
this->fn_ = fn_->xform_layer(xform_fn);
for (auto & arg : argv_)
arg = arg->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> p) override;
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
protected:
Apply(TypeDescr apply_valuetype,
const rp<Expression> & fn,
const std::vector<rp<Expression>> & argv)
: Expression(exprtype::apply, apply_valuetype),
fn_{fn}, argv_(argv)
{}
protected:
/** function to invoke **/
rp<Expression> fn_;
/** argument expressions, in l-to-r order **/
std::vector<rp<Expression>> argv_;
}; /*Apply*/
#ifdef NOT_USING
namespace detail {
/** Use:
** std::vector<ref::rp<Expression>>
**/
template <typename... Args>
struct apply_push_args;
template <>
struct apply_push_args<> {
static void push_all(std::vector<ref::rp<Expression>> * /*p_argv*/) {}
};
template <typename Arg1, typename... Rest>
struct apply_push_args<Arg1, Rest...> {
static void push_all(std::vector<ref::rp<Expression>> * p_argv,
const ref::rp<Expression> & x, Rest... rest)
{
p_argv->push_back(x);
apply_push_args<Rest...>::push_all(p_argv, rest...);
};
};
}
#endif
/* reminder: initializer-lists are compile-time only */
inline rp<Apply>
make_apply(const rp<Expression> & fn,
const std::initializer_list<rp<Expression>> args) {
std::vector<rp<Expression>> argv(args);
return Apply::make(fn, argv);
} /*make_apply*/
/** @class ApplyAccess
* @brief Apply with writeable members
*
* Convenient when scaffolding a parser,
* e.g. see xo-parser
**/
class ApplyAccess : public Apply {
public:
static rp<ApplyAccess> make_empty();
/** assign function being called to @p fn **/
void assign_fn(const rp<Expression>& fn);
/** assign expression for argument i, counting from 1.
* can use @p i = 0 as alternative to @ ref assign_fn
**/
void assign_arg(size_t i, const rp<Expression>& arg);
// inherited from GeneralizedExpression..
// void assign_valuetype(TypeDescr apply_valuetype);
private:
ApplyAccess(TypeDescr apply_valuetype,
const rp<Expression>& fn,
const std::vector<rp<Expression>>& argv)
: Apply(apply_valuetype, fn, argv) {}
};
} /*namespace scm*/
} /*namespace xo*/
/** end Apply.hpp **/

View file

@ -0,0 +1,62 @@
/* file AssignExpr.hpp
*
* author: Roland Conybeare, Aug 2024
*/
#pragma once
#include "Expression.hpp"
#include "Variable.hpp"
namespace xo {
namespace scm {
/** @class AssignExpr
* @brief Provide expression for assigning to a variable
*
* def pi = 3.14159265;
* def foo = 0.0;
* foo := pi / 2;
**/
class AssignExpr : public Expression {
public:
static rp<AssignExpr> make(const rp<Variable> & lhs,
const rp<Expression> & rhs);
static bp<AssignExpr> from(bp<Expression> x) {
return bp<AssignExpr>::from(x);
}
const rp<Variable> & lhs() const { return lhs_; }
const rp<Expression> & rhs() const { return rhs_; }
std::set<std::string> calc_free_variables() const;
// ----- inherited from Expression -----
virtual std::set<std::string> get_free_variables() const override;
virtual std::size_t visit_preorder(VisitFn visitor_fn) override;
virtual std::size_t visit_layer(VisitFn visitor_fn) override;
virtual rp<Expression> xform_layer(TransformFn xform_fn) override;
virtual void attach_envs(bp<SymbolTable> p) override;
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
private:
AssignExpr(const rp<Variable> & lhs,
const rp<Expression> & rhs);
private:
/** assign to this variable. **/
rp<Variable> lhs_;
/** assign value of this expression to variable @p lhs **/
rp<Expression> rhs_;
/** free variables for this assignment **/
std::set<std::string> free_var_set_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end AssignExpr.hpp */

View file

@ -0,0 +1,110 @@
/** @file Constant.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ConstantInterface.hpp"
#include "pretty_expression.hpp"
#include <type_traits>
namespace xo {
namespace scm {
/** @class Constant
* @brief syntax for a literal constant.
*
* Require:
* 1. T must be a POD type (plain old data)
* We need this to be true so that we can generate
* code for constructing a T instance by memcopying
* @ref value_
*
* @tp T type of captured literal.
**/
template <typename T>
class Constant : public ConstantInterface {
public:
using Reflect = xo::reflect::Reflect;
using TaggedPtr = xo::reflect::TaggedPtr;
using TypeDescr = xo::reflect::TypeDescr;
public:
/** create constant expression representing literal value x **/
static rp<Constant> make(const T & x) {
TypeDescr x_valuetype = Reflect::require<T>();
return new Constant(x_valuetype, x);
}
const T & value() const { return value_; }
// ----- ConstantInterface -----
virtual TypeDescr value_td() const override { return value_td_; }
virtual TaggedPtr value_tp() const override {
/* note: idk why, but need to spell this out in two steps with gcc 13.2 */
const void * erased_cptr = &value_;
void * erased_ptr = const_cast<void*>(erased_cptr);
return TaggedPtr(value_td_, erased_ptr);
}
// ----- Expression -----
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void display(std::ostream & os) const override {
os << "<Constant";
if (value_td_)
os << xtag("type", value_td_->short_name());
else
os << xtag("type", "nullptr");
os << xtag("value", value_);
os << ">";
}
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override {
return ppii.pps()->pretty_struct(ppii, "Constant",
rtag("type", print::quot(this->valuetype()->short_name())),
refrtag("value", value_));
}
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_;
/** value of this constant **/
T value_;
}; /*Constant*/
template <typename T>
rp<Constant<std::remove_reference_t<T>>>
make_constant(const T & x) {
return Constant<T>::make(x);
}
} /*namespace scm*/
} /*namespace xo*/
/** end Constant.hpp **/

View file

@ -0,0 +1,50 @@
/** @file ConstantInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/reflect/TypeDescr.hpp"
#include <type_traits>
namespace xo {
namespace scm {
/** @class ConstantInterface
* @brief syntax for a literal constant.
**/
class ConstantInterface : public Expression {
public:
using TaggedPtr = xo::reflect::TaggedPtr;
using TypeDescr = xo::reflect::TypeDescr;
public:
/** @p extype sets expression-type; could be constant|primitive **/
ConstantInterface(exprtype extype, TypeDescr valuetype) : Expression{extype, valuetype} {}
/** downcast from Expression **/
static bp<ConstantInterface> from(bp<Expression> x) {
return bp<ConstantInterface>::from(x);
}
/** type description for representation of literal value **/
virtual TypeDescr value_td() const = 0;
/** reflection-tagged pointer to literal value of this constant **/
virtual TaggedPtr value_tp() const = 0;
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
return std::set<std::string>();
}
virtual void attach_envs(bp<SymbolTable> /*p*/) override {}
}; /*ConstantInterface*/
} /*namespace scm*/
} /*namespace xo*/
/** end ConstantInterface.hpp **/

View file

@ -0,0 +1,110 @@
/* file ConvertExpr.hpp
*
* author: Roland Conybeare, Aug 2024
*/
#pragma once
#include "Expression.hpp"
namespace xo {
namespace scm {
/** @class Convertexpr
* @brief Convenience for automatic type conversion
*
* This is equivalent to calling a built-in primitive
* that performs the conversion.
*
* We rely on this for convenience, for example to parse
* code like
*
* def foo : i16 = 0
**/
class ConvertExpr : public Expression {
public:
static rp<ConvertExpr> make(TypeDescr dest_type,
rp<Expression> arg);
static bp<ConvertExpr> from(bp<Expression> x) {
return bp<ConvertExpr>::from(x);
}
const rp<Expression> & arg() const { return arg_; }
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override;
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += arg_->visit_preorder(visitor_fn);
return n;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += this->arg_->visit_layer(visitor_fn);
return n;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
this->arg_ = this->arg_->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> p) override {
arg_->attach_envs(p);
}
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
protected:
ConvertExpr(TypeDescr dest_type,
rp<Expression> arg)
: Expression(exprtype::convert, dest_type),
arg_{std::move(arg)}
{}
protected:
/** source expression. Convert
* @c arg_->valuetype() to @c dest_type_
**/
rp<Expression> arg_;
};
/** @class ConvertExprAccess
* @brief ConvertExpr with writeable members.
*
* Convenient when scaffolding a parser,
* e.g. see xo-parser
**/
class ConvertExprAccess : public ConvertExpr {
public:
static rp<ConvertExprAccess> make(TypeDescr dest_type,
rp<Expression> arg);
static rp<ConvertExprAccess> make_empty();
void assign_arg(rp<Expression> arg) { this->arg_ = std::move(arg); }
private:
ConvertExprAccess(TypeDescr dest_type,
rp<Expression> arg)
: ConvertExpr(dest_type,
std::move(arg))
{}
};
} /*namespace scm*/
} /*namespace xo*/
/* end ConvertExpr.hpp */

View file

@ -0,0 +1,131 @@
/* file DefineExpr.hpp
*
* author: Roland Conybeare, Jul 2024
*/
#pragma once
#include "Expression.hpp"
namespace xo {
namespace scm {
/** @class DefineExpr
* @brief Provide definition for a constant, variable or function
*
* At toplevel, introduces a new global variable.
* In a nested context,
*
* def foo = rhsexpr
* body...
*
* is equivalent to
*
* (lambda (foo) body...)(rhsexpr)
*
* Promise:
* - memory location of @ref lhs_var_ is determined when parent DefineExpr
* constructed, and is stable across calls to @ref DefineExpr::assign_lhs_name
**/
class DefineExpr : public Expression {
public:
static rp<DefineExpr> make(std::string name,
rp<Expression> value);
static bp<DefineExpr> from(bp<Expression> x) {
return bp<DefineExpr>::from(x);
}
const std::string & lhs_name() const;
const rp<Expression> & rhs() const { return rhs_; }
const rp<Variable>& lhs_variable() const { return lhs_var_; }
std::set<std::string> calc_free_variables() const;
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
return this->free_var_set_;
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += rhs_->visit_preorder(visitor_fn);
return n;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += this->rhs_->visit_layer(visitor_fn);
return n;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
this->rhs_ = this->rhs_->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> p) override {
rhs_->attach_envs(p);
}
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
protected:
/**
*
**/
DefineExpr(TypeDescr rhs_valuetype,
std::string lhs_name,
rp<Expression> rhs);
protected:
/** symbol name for this definition **/
rp<Variable> lhs_var_;
/** right-hand side of definition **/
rp<Expression> rhs_;
/** free variables for this definition **/
std::set<std::string> free_var_set_;
};
/** @class DefineExprAccess
* @brief DefineExpr with writeable members.
*
* Convenient when scaffolding a parser,
* e.g. see xo-parser
**/
class DefineExprAccess : public DefineExpr {
public:
static rp<DefineExprAccess> make(std::string lhs_name,
rp<Expression> rhs);
static rp<DefineExprAccess> make_empty();
void assign_lhs_name(const std::string & x);
void assign_rhs(const rp<Expression> & x);
private:
DefineExprAccess(TypeDescr rhs_valuetype,
std::string lhs_name,
rp<Expression> rhs)
: DefineExpr(rhs_valuetype,
std::move(lhs_name),
std::move(rhs))
{}
};
} /*namespace scm*/
} /*namespace xo*/
/* end DefineExpr.hpp */

View file

@ -0,0 +1,90 @@
/** @file Expression.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "GeneralizedExpression.hpp"
#include <functional>
#include <set>
namespace xo {
namespace scm {
class Variable; /* see Variable.hpp */
class Lambda; /* see Lamnbda.hpp */
class SymbolTable; /* see SymbolTable.hpp */
/** @class Expression
* @brief abstract syntax tree for an EGAD program
*
* (Expression Graph with Automagic Derivation)
*
* Things you can do with an Expression:
* - evaluate it using an interpreter
* - execute it on a VM
* - compile using LLVM
* see xo-jit/
*
* Expressions are immutable. This means they can resused
* across jit interactions
*
* Every expression evaluates to a value with a particular type
**/
class Expression : public GeneralizedExpression {
public:
using VisitFn = std::function
<void (bp<Expression>)>;
using TransformFn = std::function
<rp<Expression> (bp<Expression>)>;
using TypeDescr = xo::reflect::TypeDescr;
public:
explicit Expression(exprtype extype, TypeDescr valuetype)
: GeneralizedExpression(extype, valuetype) {}
/** find free named variables in this expression.
* comprises the set of names that don't match formal parameters in
* enclosing lambdas.
**/
virtual std::set<std::string> get_free_variables() const = 0;
/** visit each Expression node in this AST,
* and invoke @p fn for each.
* Returns the number of nodes visited.
* Preorder: call @p fn for a node before visiting children
**/
virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0;
/** visit each Expression node in this AST,
* including immediately-nested Lambda nodes;
* but do not recurse into the params/body of such nested Lambdas.
* Returns the number of nodes visited
**/
virtual std::size_t visit_layer(VisitFn visitor_fn) = 0;
/** traverse ast @ref visit_preorder but do not visit Lambdas **/
virtual rp<Expression> xform_layer(TransformFn visitor_fn) = 0;
/** attach an environment to each lambda expression X in this subtree,
* that will:
* - resolve names matching X's arguments (formal parameters) to
* from @p X.argv
* - resolve free variables from @p parent
**/
virtual void attach_envs(bp<SymbolTable> parent) = 0;
/** append to *p_set the set of free variables in this expression.
* returns the number of free variables introduced
*
* @param env stack of lexcically-enclosing lamnbda expressions,
* in nesting order, i.e. outermost first, innertmost last
**/
//virtual std::int32_t find_free_vars(std::vector<ref::brw<Lambda>> env) = 0;
}; /*Expression*/
} /*namespace scm*/
} /*namespace xo*/
/** end Expression.hpp **/

View file

@ -0,0 +1,33 @@
/** @file ProcedureExprInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
namespace xo {
namespace scm {
class ProcedureExprInterface : public Expression {
public:
ProcedureExprInterface(exprtype extype, TypeDescr fn_type)
: Expression(extype, fn_type) {}
/** downcast from Expression **/
static bp<ProcedureExprInterface> from(bp<Expression> x) {
return bp<ProcedureExprInterface>::from(x);
}
virtual const std::string & name() const = 0;
virtual int n_arg() const = 0; // { return this->value_td()->n_fn_arg(); }
virtual TypeDescr fn_retval() const = 0; // { return this->value_td()->fn_retval(); }
virtual TypeDescr fn_arg(uint32_t i) const = 0; // { return this->value_td()->fn_arg(i); }
private:
}; /*FunctionInterface*/
} /*namespace scm*/
} /*namespace xo*/
/** end ProcedureExprInterface.hpp **/

View file

@ -0,0 +1,72 @@
/** @file GeneralizedExpression.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/refcnt/Refcounted.hpp"
#include "xo/expression/typeinf/type_ref.hpp"
//#include "xo/reflect/TypeDescr.hpp"
#include "exprtype.hpp"
namespace xo {
namespace scm {
/** @class GeneralizedExpression
* @brief abstract syntax tree (non-executable) for schematica
*
* 'Generalized' because it includes both kernel and macro expressions.
* Every macro expression automatically translates to an equivalent kernel expression.
* Kernel expressions are directly executable.
**/
class GeneralizedExpression : public ref::Refcount {
public:
using type_ref = xo::scm::type_ref;
using prefix_type = xo::scm::prefix_type;
using TypeDescr = xo::reflect::TypeDescr;
using ppstate = xo::print::ppstate;
using ppindentinfo = xo::print::ppindentinfo;
public:
/** if @p valuetype is null, generate unique type variable
* using prefix derived from @p extype.
**/
GeneralizedExpression(exprtype extype, TypeDescr valuetype);
/** if @p valuetype is null, generate unique type variable
* name, beginning with @p prefix
**/
GeneralizedExpression(exprtype extype, prefix_type prefix, TypeDescr valuetype);
exprtype extype() const { return extype_; }
const type_ref & valuetype_ref() const { return valuetype_ref_; }
TypeDescr valuetype() const { return valuetype_ref_.td(); }
/** 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;
/** pretty printing support. See [xo-indentlog/xo/indentlog/pretty.hpp] **/
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const = 0;
/** useful when scaffolding expressions in a parser **/
void assign_valuetype(TypeDescr x) { valuetype_ref_.resolve_to(x); }
private:
/** expression type (constant | apply | ..) for this expression **/
exprtype extype_ = exprtype::invalid;
/** type information (when available) for values produced by this
* expression.
**/
type_ref valuetype_ref_;
};
inline std::ostream &
operator<<(std::ostream & os, const GeneralizedExpression & x) {
x.display(os);
return os;
}
} /*namespace scm*/
} /*namespace xo*/
/* end GeneralizedExpression.hpp */

View file

@ -0,0 +1,66 @@
/* file GlobalSymtab.hpp
*
* author: Roland Conybeare, Jun 2024
*/
#pragma once
#include "SymbolTable.hpp"
#include <map>
#include <string>
namespace xo {
namespace scm {
class GlobalSymtab : public SymbolTable {
public:
/** create instance. Probably only need one of these **/
static rp<GlobalSymtab> make_empty() { return new GlobalSymtab(); }
bp<Expression> require_global(const std::string & vname,
bp<Expression> expr);
// ----- Environment -----
virtual bool is_global_env() const override { return true; }
virtual binding_path lookup_binding(const std::string & /*vname*/) const override {
/* i_link: -1 for global environment
* j_slot: not used
*/
return { -1, 0 };
}
virtual bp<Expression> lookup_var(const std::string & vname) const override {
return this->lookup_local(vname);
}
virtual bp<Expression> lookup_local(const std::string & vname) const override {
auto ix = global_map_.find(vname);
if (ix == global_map_.end()) {
/* not found */
return bp<Variable>();
}
return ix->second;
}
virtual void upsert_local(bp<Variable> target) override;
virtual void print(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const xo::print::ppindentinfo & ppii) const override;
private:
GlobalSymtab();
private:
/* for assignable globals, need to allocate memory
* addresses for these.
*/
std::map<std::string, rp<Expression>> global_map_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end GlobalSymtab.hpp */

View file

@ -0,0 +1,174 @@
/** @file IfExpr.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <vector>
#include <string>
//#include <cstdint>
namespace xo {
namespace scm {
/** @class IfExpr
* @brief abstract syntax tree for a function definition
*
**/
class IfExpr : public Expression {
public:
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
**/
static rp<IfExpr> make(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & when_false);
/** downcast from Expression **/
static bp<IfExpr> from(bp<Expression> x) {
return bp<IfExpr>::from(x);
}
const rp<Expression> & test() const { return test_; }
const rp<Expression> & when_true() const { return when_true_; }
const rp<Expression> & when_false() const { return when_false_; }
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> retval = test_->get_free_variables();
std::set<std::string> free_vars;
free_vars = when_true_->get_free_variables();
for (const auto & s : free_vars)
retval.insert(s);
free_vars = when_false_->get_free_variables();
for (const auto & s : free_vars)
retval.insert(s);
return retval;
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += this->test_->visit_preorder(visitor_fn);
n += this->when_true_->visit_preorder(visitor_fn);
n += this->when_false_->visit_preorder(visitor_fn);
return n;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
n += this->test_->visit_layer(visitor_fn);
n += this->when_true_->visit_layer(visitor_fn);
n += this->when_false_->visit_layer(visitor_fn);
return n;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
this->test_ = this->test_->xform_layer(xform_fn);
this->when_true_ = this->when_true_->xform_layer(xform_fn);
this->when_false_= this->when_false_->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> p) override {
test_->attach_envs(p);
when_true_->attach_envs(p);
when_false_->attach_envs(p);
}
#ifdef NOT_USING
virtual std::int32_t find_free_vars(std::set<bp<Variable>> * p_set) override {
return (test_->find_free_vars(p_set)
+ when_true_->find_free_vars(p_set)
+ when_false_->find_free_vars(p_set));
}
#endif
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppi) const override;
protected:
/**
* @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,
rp<Expression> test,
rp<Expression> when_true,
rp<Expression> when_false)
: Expression(exprtype::ifexpr, ifexpr_type),
test_{std::move(test)},
when_true_{std::move(when_true)},
when_false_{std::move(when_false)} {}
static TypeDescr check_consistent_valuetype(const rp<Expression> & when_true,
const rp<Expression> & when_false);
/** determine if-expr valuetype **/
void establish_valuetype();
protected:
/** if:
* (if x y z)
*
* executes x; if true execute y; otherwise execute z
**/
rp<Expression> test_;
rp<Expression> when_true_;
rp<Expression> when_false_;
}; /*IfExpr*/
inline rp<IfExpr>
make_ifexpr(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & when_false)
{
return IfExpr::make(test, when_true, when_false);
}
class IfExprAccess : public IfExpr {
public:
static rp<IfExprAccess> make(rp<Expression> test,
rp<Expression> when_true,
rp<Expression> when_false);
static rp<IfExprAccess> make_empty();
void assign_test(rp<Expression> x) { test_ = std::move(x); }
void assign_when_true(rp<Expression> x);
void assign_when_false(rp<Expression> x);
private:
IfExprAccess(TypeDescr ifexpr_type,
rp<Expression> test,
rp<Expression> when_true,
rp<Expression> when_false)
: IfExpr(ifexpr_type,
std::move(test),
std::move(when_true),
std::move(when_false)) {}
};
} /*namespace scm*/
} /*namespace xo*/
/** end IfExpr.hpp **/

View file

@ -0,0 +1,242 @@
/** @file Lambda.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
#include "ProcedureExprInterface.hpp"
#include "Variable.hpp"
#include "LocalSymtab.hpp"
#include <map>
#include <vector>
#include <string>
namespace xo {
namespace scm {
/** @class Lambda
* @brief abstract syntax tree for a function definition
*
**/
class Lambda : public ProcedureExprInterface {
public:
/**
* @p name. Name for this lambda -- must be unique
* @p lambda_type. Function signature
* @p local_env. Environment with formals as content
* @p body. Expression for lambda function body
**/
static rp<Lambda> make(const std::string & name,
TypeDescr lambda_type,
const rp<LocalSymtab> & local_env,
const rp<Expression> & body);
/**
* @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
* @p parent_env Environment for enclosing lexical scope
**/
static rp<Lambda> make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body,
const rp<SymbolTable> & parent_env);
/**
* @p name Name for this lambda -- must be unique
* @p env Environment with {name,type} for each formal parameter
* @p body Expression for body of function
**/
static rp<Lambda> make_from_env(const std::string & name,
const rp<LocalSymtab> & env,
TypeDescr explicit_return_td,
const rp<Expression> & body);
/** create type description for lambda with arguments @p argv
* and return type @p return_td
**/
static TypeDescr assemble_lambda_td(const std::vector<rp<Variable>> & argv,
TypeDescr return_td);
/** create type description for lambda with arguments @p argv
* and body expression @p body.
* @p explicit_return_td will be used if non-null.
* otherwise use @p body valuetype
**/
static TypeDescr assemble_lambda_td(const std::vector<rp<Variable>> & argv,
TypeDescr explicit_return_td,
const rp<Expression> & body);
/** downcast from Expression **/
static bp<Lambda> from(bp<Expression> x) {
return bp<Lambda>::from(x);
}
const std::string & type_str() const { return type_str_; }
const std::vector<rp<Variable>> & argv() const { return local_env_->argv(); }
const rp<Expression> & body() const { return body_; }
const std::string& i_argname(int i_arg) const { return local_env_->lookup_arg(i_arg)->name(); }
bool needs_closure_flag() const { return !free_var_set_.empty(); }
bool is_captured(const std::string& var) const { return (captured_var_set_.find(var) != captured_var_set_.end()); }
// ----- FunctionInterface -----
virtual const std::string & name() const override { return name_; }
/** return number of arguments expected by this function **/
virtual int n_arg() const override { return local_env_->n_arg(); }
virtual TypeDescr fn_retval() const override { return body_->valuetype(); }
virtual TypeDescr fn_arg(uint32_t i) const override { return local_env_->fn_arg(i); }
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
return this->free_var_set_;
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
for (const auto & arg : local_env_->argv())
n += arg->visit_preorder(visitor_fn);
n += body_->visit_preorder(visitor_fn);
return n;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
std::size_t n = 1;
visitor_fn(this);
return n;
}
virtual rp<Expression> xform_layer(TransformFn /*xform_fn*/) override {
/* a layer is bounded by lambdas, don't enter them */
return this;
}
virtual void attach_envs(bp<SymbolTable> p) override;
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
protected:
/** create string description for function signature,
* consistent with c++ expectation
**/
static std::string assemble_type_str(TypeDescr lambda_td);
/** @param lambda_type. function type for this lambda.
* We arbitrarily choose the form "Retval(*)(Args...)"
**/
Lambda(const std::string & name,
TypeDescr lambda_type,
const rp<LocalSymtab> & local_env,
const rp<Expression> & body);
/** compute free-variable set for this lambda **/
std::set<std::string> calc_free_variables() const;
/** ensure at most one Variable instance with a particular name
* in this lambda, but ignore nested lambdas.
*
* Goal is to unify variables that can use the same binding
* path to determine memory location at runtime.
**/
std::map<std::string, rp<Variable>> regularize_layer_vars();
/** compute derived members
* (type_str_, free_var_set_, captured_var_set_, layer_var_map_,
* nested_lambda_map_)
* once .body_ is established
**/
void complete_assembly_from_body();
protected:
/** lambda name. Initially supporting only form like
* (define (foo x y z)
* (+ (* x x) (* y y) (* z z)))
*
* In any case need to supply names for distinct
* things-for-which-code-is-generated so that they can be linked etc.
**/
std::string name_;
/** e.g.
* "double(double,double)" for function of two doubles that
* returns a double
**/
std::string type_str_;
/** function body **/
rp<Expression> body_;
/** free variables for this lambda **/
std::set<std::string> free_var_set_;
/** variables that appear free in some nested lambda **/
std::set<std::string> captured_var_set_;
/** map giving unique identity to each variable appearing in this layer.
* includes:
* - formal parameters
* - free variables in @ref body_
* excludes:
* - any variables appearing in nested lambdas
* (whether formals or free variables)
**/
std::map<std::string, rp<Variable>> layer_var_map_;
/** all lambdas nested once inside this lambda's body **/
std::map<std::string, bp<Lambda>> nested_lambda_map_;
/** established (once) by @ref attach_envs.
*
* @note data dependency on ancestor expressions that don't exist yet
* when Lambda constructor runs, so we need to assign @ref local_env_
* later.
**/
rp<LocalSymtab> local_env_;
}; /*Lambda*/
inline rp<Lambda>
make_lambda(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body,
const rp<SymbolTable> & parent_env)
{
return Lambda::make(name, argv, body, parent_env);
}
class LambdaAccess : public Lambda {
public:
static rp<LambdaAccess> make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body,
const rp<SymbolTable> & parent_env);
static rp<LambdaAccess> make_empty();
/** assign body + compute derived members
* (see complete_assembly_from_body())
**/
void assign_body(const rp<Expression> & body);
private:
/** lambda_type, body can be null here,
* in which case fill in with assign methods
**/
LambdaAccess(const std::string & name,
TypeDescr lambda_type,
const rp<LocalSymtab> & local_env,
const rp<Expression> & body);
};
} /*namespace scm*/
} /*namespace xo*/
/** end Lambda.hpp **/

View file

@ -0,0 +1,123 @@
/* file LocalSymtab.hpp
*
* author: Roland Conybeare, Jun 2024
*/
#pragma once
#include "SymbolTable.hpp"
#include "Variable.hpp"
#include "xo/reflect/TypeDescr.hpp"
namespace xo {
namespace scm {
class Lambda;
/** @brief LocalEnv
*
* @class Local environment for a lambda.
* Lists the Variables corresponding to this lambda's formal
* parameters, but also links to @ref Environment for
* innermost enclosing @ref Lambda.
**/
class LocalSymtab : public SymbolTable {
public:
using TypeDescr = xo::reflect::TypeDescr;
public:
static rp<LocalSymtab> make_empty();
/** named ctor idiom. Create instance with local variables per @p argv **/
static rp<LocalSymtab> make(const std::vector<rp<Variable>> & argv,
const rp<SymbolTable> & parent_env);
/** Create instance with single local variable @ap argv1 **/
static rp<LocalSymtab> make1(const rp<Variable> & arg1,
const rp<SymbolTable> & parent_env);
/** runtime downcast. nullptr if @p x is not a LocalEnv instance **/
static bp<LocalSymtab> from(const bp<SymbolTable> & x) { return bp<LocalSymtab>::from(x); }
Lambda * origin() const { return origin_; }
const std::vector<rp<Variable>> & argv() const { return argv_; }
const rp<Variable>& lookup_arg(int i) const { return argv_[i]; }
int n_arg() const { return argv_.size(); }
TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); }
/** report binding path for a formal parameter.
* Returns sentinel if @p vname doesn't appear in @ref argv_
**/
binding_path lookup_local_binding(const std::string & vname) const;
/** single-assign this environment's origin **/
void assign_origin(Lambda * p) {
assert(origin_ == nullptr);
origin_ = p;
}
/** single-assign this environment's parent **/
void assign_parent(bp<SymbolTable> p);
// ----- Environment -----
virtual bool is_global_env() const override { return false; }
virtual binding_path lookup_binding(const std::string & vname) const override;
virtual bp<Expression> lookup_var(const std::string & vname) const override {
bp<Expression> retval = this->lookup_local(vname);
if (retval)
return retval;
/* here: target not found in local vars,
* delegate to innermost ancestor
*/
return parent_env_->lookup_var(vname);
}
virtual bp<Expression> lookup_local(const std::string & vname) const override {
for (const auto & arg : argv_) {
if (arg->name() == vname)
return arg;
}
return bp<Expression>();
}
/** create/replace local variable @p target.
* Narrow use case: intended for when LocalEnv represents a top-level session environment.
**/
virtual void upsert_local(bp<Variable> target) override;
virtual void print(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const print::ppindentinfo & ppii) const override;
private:
LocalSymtab(const std::vector<rp<Variable>> & argv, const rp<SymbolTable> & parent_env);
private:
/** Lambda for which this environment created.
*
* Invariant:
* @code
* origin_->local_env_ == this
* @endcode
**/
Lambda * origin_ = nullptr;
/** formal argument names.
* all variables in @ref argv_ have distinct names.
* if @c .lookup_binding(vname) returns a binding path with @c .i_link=0 and @c .j_slot=j
* then @c argv_[j]->name_ is @c vname.
**/
std::vector<rp<Variable>> argv_;
/** parent environment. A free variable in this lambda's
* body will be resolved by referring them to @ref parent_env_.
**/
rp<SymbolTable> parent_env_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end LocalSymtab.hpp */

View file

@ -0,0 +1,219 @@
/** @file PrimitiveExpr.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "PrimitiveExprInterface.hpp"
#include "pretty_expression.hpp"
#include "llvmintrinsic.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/indentlog/print/quoted.hpp"
extern "C" {
/* these symbols needed to link primitives */
/* see Primitive_f64::make() */
double add2_f64(double x, double y);
};
namespace xo {
namespace scm {
/** @class PrimitiveExpr
* @brief syntax for a constant that refers to a known function.
*
* Two cases here:
* 1. (always) primitive refers to a compiled (C/C++) function that we can invoke at runtime
* 2. (sometimes) primitive also refers to a function that is supported directly in llvm
* (e.g. floating-point addition). In that case @ref intrinsic_
* identifies that direct support, provided it knows at codegen time which primitive
* is being invoked
*
* 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 PrimitiveExpr: public PrimitiveExprInterface {
public:
using Reflect = xo::reflect::Reflect;
using TaggedPtr = xo::reflect::TaggedPtr;
using TypeDescr = xo::reflect::TypeDescr;
using fptr_type = FunctionPointer;
public:
static rp<PrimitiveExpr> make(const std::string & name,
FunctionPointer fnptr,
bool explicit_symbol_def,
llvmintrinsic intrinsic) {
TypeDescr fn_type = Reflect::require<FunctionPointer>();
return new PrimitiveExpr(fn_type, name, fnptr, explicit_symbol_def, intrinsic);
}
/** see classes below for intrinsics **/
FunctionPointer value() const { return value_; }
TypeDescr value_td() const { return value_td_; }
// ----- PrimitiveExprInterface -----
virtual TaggedPtr value_tp() const final override {
/* note: idk why, but need to spell this out in two steps with gcc 13.2 */
const void * erased_cptr = &value_;
void * erased_ptr = const_cast<void*>(erased_cptr);
return TaggedPtr(value_td_, erased_ptr);
}
virtual llvmintrinsic intrinsic() const override { return intrinsic_; }
virtual bool explicit_symbol_def() const override { return explicit_symbol_def_; }
virtual void_function_type function_address() const override { return reinterpret_cast<void_function_type>(value_); }
// ----- FunctionInterface -----
virtual std::string const & name() const override { return name_; }
virtual int n_arg() const override { return this->value_td()->n_fn_arg(); }
virtual TypeDescr fn_retval() const override { return this->value_td()->fn_retval(); }
virtual TypeDescr fn_arg(uint32_t i) const override { return this->value_td()->fn_arg(i); }
// ----- Expression -----
virtual void display(std::ostream & os) const override {
os << "<PrimitiveExpr"
<< xtag("name", name_)
<< xtag("type", this->value_td()->short_name())
<< xtag("value", this->value())
<< ">";
}
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override {
/* 1. rtag instead of refrtag:
* print::quot() is a temporary rvalue; lifetime ends before control enters pretty_struct()
*
* 2. value cast to void*:
* we don't have pretty printer for native function pointers anyway
* + simplifies ppdetail_atomic
*/
return ppii.pps()->pretty_struct(ppii, "PrimitiveExpr",
refrtag("name", name_),
rtag("type", print::quot(this->valuetype()->short_name())),
refrtag("value", (void*)(this->value())));
}
private:
PrimitiveExpr(TypeDescr fn_type,
const std::string & name,
FunctionPointer fnptr,
bool explicit_symbol_def,
llvmintrinsic intrinsic)
: PrimitiveExprInterface(fn_type),
name_{name},
value_td_{Reflect::require_function<FunctionPointer>()},
value_{fnptr},
explicit_symbol_def_{explicit_symbol_def},
intrinsic_{intrinsic}
{
if (!value_td_->is_function())
throw std::runtime_error("PrimitiveExpr: expected function pointer");
if (!value_td_->fn_retval())
throw std::runtime_error("PrimitiveExpr: expected non-null function return value");
}
private:
// from Expression:
// exprtype extype_
/** name of this primitive, e.g. '+', 'sqrt' **/
std::string name_;
/** type description for FunctionPointer **/
TypeDescr value_td_;
/** address of executable function **/
FunctionPointer value_;
/** for LLVM: if true, use Jit.intern_symbol() to provide explicit binding.
*
* Not obvious what distinguishes functions like ::sin(), ::sqrt()
* (which work without this) from symbols like ::mul_i32(), which require it.
**/
bool explicit_symbol_def_ = false;
/** invalid: generate call (IRBuilder::CreateCall)
* all others: generate direct use of LLVM intrinsic
**/
llvmintrinsic intrinsic_;
}; /*PrimitiveExpr*/
/** adopt function @p x as a callable primitive function named @p name **/
template <typename FunctionPointer>
rp<PrimitiveExpr<FunctionPointer>>
make_primitive(const std::string & name,
FunctionPointer x,
bool explicit_symbol_def,
llvmintrinsic intrinsic)
{
return PrimitiveExpr<FunctionPointer>::make(name, x, explicit_symbol_def, intrinsic);
}
// NOTE: see xo-reader/src/reader/progress_xs.cpp
// binding operators to primitive applications.
/** builtin primitives :: i64 x i64 -> bool **/
class PrimitiveExpr_cmp_i64 : public PrimitiveExpr<bool (*)(std::int64_t, std::int64_t)> {
public:
using PrimitiveExprType = PrimitiveExpr<bool (*)(std::int64_t, std::int64_t)>;
public:
/** eq2_i64: compare two 64-bit integers for equality **/
static rp<PrimitiveExprType> make_cmp_eq2_i64();
/** ne2_i64: compare two 64-bit integers for inequality **/
static rp<PrimitiveExprType> make_cmp_ne2_i64();
/** lt2_i64: compare two 64-bit integers for lessthan **/
static rp<PrimitiveExprType> make_cmp_lt2_i64();
/** lt2_i64: compare two 64-bit integers for lessthanorequal **/
static rp<PrimitiveExprType> make_cmp_le2_i64();
/** gt2_i64: compare two 64-bit integers for greaterthan **/
static rp<PrimitiveExprType> make_cmp_gt2_i64();
/** ge2_i64: compare two 64-bit integers for greaterthan **/
static rp<PrimitiveExprType> make_cmp_ge2_i64();
};
/** builtin primitives :: i64 x i64 -> i64 **/
class PrimitiveExpr_i64 : public PrimitiveExpr<std::int64_t (*)(std::int64_t, std::int64_t)> {
public:
using PrimitiveExprType = PrimitiveExpr<std::int64_t (*)(std::int64_t, std::int64_t)>;
public:
/** add2_i64: add two 64-bit integers **/
static rp<PrimitiveExprType> make_add2_i64();
/** sub2_i64: subtract two 64-bit integers **/
static rp<PrimitiveExprType> make_sub2_i64();
/** mul2_i64: multiply two 64-bit integers **/
static rp<PrimitiveExprType> make_mul2_i64();
/** div2_i64: divide two 64-bit integers **/
static rp<PrimitiveExprType> make_div2_i64();
};
/** builtin primitives :: f64 x f64 -> f64 **/
class PrimitiveExpr_f64 : public PrimitiveExpr<double (*)(double, double)> {
public:
using PrimitiveExprType = PrimitiveExpr<double (*)(double, double)>;
public:
/** add2_f64: add two 64-bit floating-point numbers **/
static rp<PrimitiveExprType> make_add2_f64();
/** sub2_f64: subtract two 64-bit floating-point numbers **/
static rp<PrimitiveExprType> make_sub2_f64();
/** mul2_f64: multiply two 64-bit floating-point numbers **/
static rp<PrimitiveExprType> make_mul2_f64();
/** div2_f64: divide two 64-bit floating-point numbers **/
static rp<PrimitiveExprType> make_div2_f64();
};
} /*namespace scm*/
} /*namespace xo*/
/** end PrimitiveExpr.hpp **/

View file

@ -0,0 +1,82 @@
/** @file PrimitiveExprInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ProcedureExprInterface.hpp"
#include "llvmintrinsic.hpp"
#include "xo/reflect/TaggedPtr.hpp"
#include <type_traits>
namespace xo {
namespace scm {
class PrimitiveExprInterface : public ProcedureExprInterface {
public:
using TaggedPtr = xo::reflect::TaggedPtr;
using void_function_type = void (*)();
public:
explicit PrimitiveExprInterface(TypeDescr fn_type)
: ProcedureExprInterface(exprtype::primitive, fn_type) {}
/** downcast from Expression **/
static bp<PrimitiveExprInterface> from(bp<Expression> x) {
return bp<PrimitiveExprInterface>::from(x);
}
/** @return executable function as tagged pointer.
* Load-bearing for xo-interpreter.
**/
virtual TaggedPtr value_tp() const = 0;
/** if true, Jit will try to explicitly symbol for this primitive
* (instead of looking it up in host process).
* Don't know if this works.
* Do know it's not needed for ::sin(), ::sqrt().
* Do know that my extern "C" functions like ::mul_i32(), ::mul_f64()
* need something else.
**/
virtual bool explicit_symbol_def() const = 0;
/** function address for this primitive **/
virtual void_function_type function_address() const = 0;
/** get llvm intrinsic hint for this primitive **/
virtual llvmintrinsic intrinsic() const = 0;
// virtual const std::string & name() const;
// virtual int n_arg() const;
// virtual TypeDescr fn_retval() const;
// virtual TypeDescr fn_arg(uint32_t i) const;
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
return std::set<std::string>();
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> /*p*/) override {}
private:
}; /*PrimitiveInterface*/
} /*namespace scm*/
} /*namespace xo*/
/** end PrimitiveInterface.hpp **/

View file

@ -0,0 +1,33 @@
/** @file FunctionExprInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
namespace xo {
namespace scm {
class ProcedureExprInterface : public Expression {
public:
ProcedureExprInterface(exprtype extype, TypeDescr fn_type)
: Expression(extype, fn_type) {}
/** downcast from Expression **/
static bp<ProcedureExprInterface> from(bp<Expression> x) {
return bp<ProcedureExprInterface>::from(x);
}
virtual const std::string & name() const = 0;
virtual int n_arg() const = 0; // { return this->value_td()->n_fn_arg(); }
virtual TypeDescr fn_retval() const = 0; // { return this->value_td()->fn_retval(); }
virtual TypeDescr fn_arg(uint32_t i) const = 0; // { return this->value_td()->fn_arg(i); }
private:
}; /*FunctionInterface*/
} /*namespace scm*/
} /*namespace xo*/
/** end FunctionExprInterface.hpp **/

View file

@ -0,0 +1,55 @@
/** @file Sequence.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
namespace xo {
namespace scm {
class Sequence : public Expression {
public:
Sequence(const std::vector<rp<Expression>> & xv)
: Expression(exprtype::sequence,
xv[xv.size() - 1]->valuetype()),
expr_v_(xv) {}
static rp<Sequence> make(const std::vector<rp<Expression>> & xv) { return new Sequence(xv); }
/** downcast from Expression **/
static bp<Sequence> from(bp<Expression> x) {
return bp<Sequence>::from(x);
}
std::size_t size() const { return expr_v_.size(); }
const rp<Expression> & operator[](std::size_t i) const { return expr_v_[i]; }
// ----- from Expression -----
/** note: broken if .expr_v_ contains any def-exprs
* (will treat references to so-defined vars as free).
* must rewrite these first
**/
virtual std::set<std::string> get_free_variables() const override;
virtual std::size_t visit_preorder(VisitFn visitor_fn) override;
/** note: borken if .expr_v_ contains any def-exprs **/
virtual std::size_t visit_layer(VisitFn visitor_fn) override;
virtual rp<Expression> xform_layer(TransformFn visitor_fn) override;
virtual void attach_envs(bp<SymbolTable> parent) override;
// ----- from GeneralizedExpression ----
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
private:
/** sequence of expressions; evaluate in left-to-right order.
**/
std::vector<rp<Expression>> expr_v_;
};
} /*namespace scm*/
} /*namespace xo*/
/** end Sequence.hpp **/

View file

@ -0,0 +1,68 @@
/* file SymbolTable.hpp
*
* author: Roland Conybeare, Jun 2024
*/
#pragma once
#include "xo/refcnt/Refcounted.hpp"
#include "Variable.hpp"
#include "binding_path.hpp"
#include "xo/indentlog/print/pretty.hpp"
namespace xo {
namespace scm {
class Expression;
/** @class Environment
* @brief Abstract interface for tracking variable bindings
*
* When parsing (see xo-reader): rhs will always be a variable.
* When generating code (see xo-jit): rhs can be any expression,
* for example a Lambda.
**/
class SymbolTable : public ref::Refcount {
public:
/** true if this is toplevel (global) environment.
* Toplevel environment doesn't have slot numbers.
*
* Variables that bind in the global environment have unique
* names, which we rely on instead of slot numbers.
**/
virtual bool is_global_env() const = 0;
/** lookup binding path for @p vname in this environment.
*
* Reports ingredients needed to address variable at runtime,
* in runtime analog of this environment
**/
virtual binding_path lookup_binding(const std::string & vname) const = 0;
/** lookup variable-expression @p vname in this environment.
* returns llvm::Value representing code that produces a value for vname
**/
virtual bp<Expression> lookup_var(const std::string & vname) const = 0;
/** like @ref lookup_var but do not delegate to parent environment **/
virtual bp<Expression> lookup_local(const std::string & vname) const = 0;
/** create/replace local variable @p target.
* Narrow use case: intended for when Environment represents a top-level session environment
**/
virtual void upsert_local(bp<Variable> target) = 0;
virtual void print(std::ostream & os) const = 0;
virtual std::uint32_t pretty_print(const xo::print::ppindentinfo & ppii) const = 0;
};
inline std::ostream &
operator<< (std::ostream & os, const SymbolTable & x) {
x.print(os);
return os;
}
} /*namespace scm*/
} /*namespace xo*/
/* end SymbolTable.hpp */

View file

@ -0,0 +1,98 @@
/** @file Variable.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
#include "binding_path.hpp"
namespace xo {
namespace scm {
/** @class Variable
* @brief syntax for a variable reference
**/
class Variable : public Expression {
public:
/** Generate unique symbol-name beginning with @p prefix.
* Relies on static counter
**/
static std::string gensym(const std::string & prefix);
/** create expression representing a variable
* identified by @p name, that can take on values
* described by @p var_type.
**/
static rp<Variable> make(const std::string & name,
TypeDescr var_type) {
return new Variable(name, var_type);
}
/** return copy of @p x: same var, different object identity **/
static rp<Variable> copy(bp<Variable> x) {
return new Variable(x->name(), x->valuetype());
}
/** downcast from Expression **/
static bp<Variable> from(bp<Expression> x) {
return bp<Variable>::from(x);
}
void assign_name(const std::string & name) { name_ = name; }
const std::string & name() const { return name_; }
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> retval;
retval.insert(this->name_);
return retval;
}
virtual std::size_t visit_preorder(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual std::size_t visit_layer(VisitFn visitor_fn) override {
visitor_fn(this);
return 1;
}
virtual rp<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void attach_envs(bp<SymbolTable> /*p*/) override;
virtual void display(std::ostream & os) const override;
virtual std::uint32_t pretty_print(const ppindentinfo & ppii) const override;
private:
Variable(const std::string & name,
TypeDescr var_type)
: Expression(exprtype::variable, var_type),
name_{name} {}
private:
/** variable name **/
std::string name_;
/** Eventually: navigate environment via this path to find runtime memory
* location for this variable.
*
* Establish via @ref attach_envs
**/
binding_path path_;
}; /*Variable*/
inline rp<Variable>
make_var(const std::string & name,
reflect::TypeDescr var_type) {
return Variable::make(name, var_type);
}
} /*namespace scm*/
} /*namespace xo*/
/** end Variable.hpp **/

View file

@ -0,0 +1,29 @@
/* file binding_path.hpp
*
* author: Roland Conybeare, Jul 2024
*/
#pragma once
namespace xo {
namespace scm {
/** @class path
*
* @brief path from the *use* of a variable to the environment
* providing its location.
**/
struct binding_path {
/** @of parent links to traverse. -1 if global. -2 if sentinel **/
int i_link_ = -2;
/** for variables bound in some local environment:
* slot# within that environment.
*
* Ignored if @ref i_link_ is -1
**/
int j_slot_ = 0;
}; /*binding_path*/
} /*namespace scm*/
} /*namespace xo*/
/* end binding_path.hpp */

View file

@ -0,0 +1,81 @@
/** @file exprtype.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include <ostream>
//#include <cstdint>
namespace xo {
namespace scm {
/** @enum exprtype
* @brief enum to identify subclasses of xo::scm::Expression.
*
**/
enum class exprtype {
/** sentinel value **/
invalid = -1,
/** literal constant. must satisfy both standard_layout_type + trivial **/
constant,
/** a literal constant that refers to a linkable named function **/
primitive,
/** variable/function definition **/
define,
/** variable assignment **/
assign,
/** function call **/
apply,
/** function definition **/
lambda,
/** variable reference **/
variable,
/** if-then-else **/
ifexpr,
/** sequence **/
sequence,
/** type conversion **/
convert,
/** not an expression. comes last, counts entries **/
n_expr
};
inline const char *
expr2str(exprtype x)
{
switch(x) {
case exprtype::invalid: return "?exprtype";
case exprtype::constant: return "constant";
case exprtype::primitive: return "primitive";
case exprtype::define: return "define";
case exprtype::assign: return "assign";
case exprtype::apply: return "apply";
case exprtype::lambda: return "lambda";
case exprtype::variable: return "variable";
case exprtype::ifexpr: return "if_expr";
case exprtype::sequence: return "sequence";
case exprtype::convert: return "convert";
default: break;
}
return "???exprtype???";
}
/** @brief number of built-in expression types, repr convenient for array sizing **/
static constexpr std::size_t n_exprtype = static_cast<std::size_t>(exprtype::n_expr);
inline std::ostream &
operator<<(std::ostream & os,
exprtype x)
{
os << expr2str(x);
return os;
}
} /*namespace scm*/
} /*namespace xo*/
/** end exprtype.hpp **/

View file

@ -0,0 +1,159 @@
/** @file llvmintrinsic.hpp
*
* Author: Roland Conybeare
**/
#pragma once
//#include <cstdint>
namespace xo {
namespace scm {
/** @enum llvminstrinsic
* @brief enum to identify an LLVM instrinsic, e.g. @c IRBuilder::CreateFAdd
*
* Associate an @c llvminstrinsic with an AST @c Primitive p.
* Later, in @c xo::jit::IrPipeline
* - when generating code for @c xo::scm::Apply
* - with *p* is in the function-call position
* can use the associated llvm instrinsic instead of generating a function call
* @c Primitive::value
*
* @note llvm will still sometimes need to use
* @c Primitive::value (and generate a function call sequence),
* for example when handling an @c xo::scm::Apply instance
* where the function position is a computed function.
* @endnote
*
* @note
* LLVM requires separate intrinsics for {ints, floats}.
* It does not need separate intrinsics for different sizes.
* For example IRBuilder::CreateAdd works for
* {8-bit, 16-bit, 32-bit, 64-bit, 128-bit} x {signed, unsigned} integers
* Integer division is an exception; need to choose between i_sdiv and i_udiv
* @endnote
*
* @note
* NSW stands for 'no signed wrap' -> poison value if overflow (costs more)
* NUW stands for 'no unsigned wrap' -> poison value if overflow (costs more)
* @endnote
*
* See: xo-jit/src/jit/MachPipeline.cpp
**/
enum class llvmintrinsic {
// see /nix/store/x5yz...llvm-18.1.5-dev/include/llvm/IR/IRBuilder.h
/** sentinel value **/
invalid = -1,
/** -> IRBuilder::CreateNeg (negate 1 integer) **/
i_neg,
/** -> IRBuilder::CreateAdd (add 2 integers, overflow silently) **/
i_add,
/** -> IRBuilder::CreateSub (subtract 2 integers, overflow silently) **/
i_sub,
/** -> IRBuilder::CreateMul (multiply 2 integers, overflow silently) **/
i_mul,
/** -> IRBuilder::CreateSdiv (divide 2 signed integers) **/
i_sdiv,
/** -> IRBuilder::CreateUdiv (divide 2 unsigned integers) **/
i_udiv,
/** -> IRBuilder::CreateICmpEQ (test integers for equality) **/
i_eq,
/** -> IRBuilder::CreateICmpNE (test integers for inequality) **/
i_ne,
/** -> IRBuilder::CreateICmpSGT (test signed integers for greater) **/
i_sgt,
/** -> IRBuilder::CreateICmpSGE (test signed integers for greater-or-equal) **/
i_sge,
/** -> IRBuilder::CreateICmpSLT (test signed integers for lesser) **/
i_slt,
/** -> IRBuilder::CreateCmpSLE (test signed integers for lesser-or-equal) **/
i_sle,
// TODO: unsigned comparisons
/** -> IRBuilder::CreateFAdd (add 2 floating-point numbers) **/
fp_add,
/** -> IRBuilder::CreateFSub (subtract 2 floating-pointer numbers) **/
fp_sub,
/** -> IRBuilder::CreateFMul (multiply 2 floating-point numbers) **/
fp_mul,
/** -> IRBuilder::CreateFDiv (divide 2 floating-point numbers) **/
fp_div,
// TODO: floating-point comparisons
/**
* want to do whatever llvm IR @c llvm.sqrt.f64 and friends do.
* Not sure if that's an always-available function of something else
**/
fp_sqrt,
/** WIP **/
fp_pow,
/** WIP **/
fp_sin,
/** WIP **/
fp_cos,
/** WIP **/
fp_tan,
/** not an intrinsic. comes last, counts entries **/
n_intrinsic
};
inline const char *
llvmintrinsic2str(llvmintrinsic x)
{
switch(x) {
case llvmintrinsic::invalid: return "?llvminstrinsic";
case llvmintrinsic::i_neg: return "i_neg";
case llvmintrinsic::i_add: return "i_add";
case llvmintrinsic::i_sub: return "i_sub";
case llvmintrinsic::i_mul: return "i_mul";
case llvmintrinsic::i_sdiv: return "i_sdiv";
case llvmintrinsic::i_udiv: return "i_udiv";
case llvmintrinsic::i_eq: return "i_eq";
case llvmintrinsic::i_ne: return "i_ne";
case llvmintrinsic::i_sgt: return "i_sgt";
case llvmintrinsic::i_sge: return "i_sge";
case llvmintrinsic::i_slt: return "i_slt";
case llvmintrinsic::i_sle: return "i_sle";
case llvmintrinsic::fp_add: return "fp_add";
case llvmintrinsic::fp_sub: return "fp_sub";
case llvmintrinsic::fp_mul: return "fp_mul";
case llvmintrinsic::fp_div: return "fp_div";
case llvmintrinsic::fp_sqrt: return "fp_sqrt";
case llvmintrinsic::fp_pow: return "fp_pow";
case llvmintrinsic::fp_sin: return "fp_sin";
case llvmintrinsic::fp_cos: return "fp_cos";
case llvmintrinsic::fp_tan: return "fp_tan";
default: break;
}
return "???llvmintrinsic???";
} /*llvmintrinsic2str*/
} /*namespace scm*/
} /*namespace xo*/
/** end llvmintrinsic.hpp **/

View file

@ -0,0 +1,31 @@
/* @file pretty_expression.hpp
*
* author: Roland Conybeare, Jul 2025
*/
#pragma once
#include "xo/indentlog/print/pretty.hpp"
#include "xo/refcnt/pretty_refcnt.hpp"
#include "Expression.hpp"
namespace xo {
namespace print {
template<>
struct ppdetail<xo::scm::GeneralizedExpression> {
static bool print_pretty(const ppindentinfo & ppii,
const xo::scm::GeneralizedExpression & x) {
return x.pretty_print(ppii);
}
};
template <>
struct ppdetail<xo::scm::Expression> {
static bool print_pretty(const ppindentinfo & ppii,
const xo::scm::Expression & x) {
return x.pretty_print(ppii);
}
};
}
}

View file

@ -0,0 +1,39 @@
/* @file pretty_localenv.hpp */
#pragma once
#include "xo/indentlog/print/pretty.hpp"
#include "xo/refcnt/pretty_refcnt.hpp"
#include "LocalSymtab.hpp"
namespace xo {
namespace print {
template <>
struct ppdetail<xo::scm::SymbolTable> {
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::SymbolTable & x) {
return x.pretty_print(ppii);
}
};
template <>
struct ppdetail<xo::scm::LocalSymtab> {
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::LocalSymtab & x) {
return x.pretty_print(ppii);
}
};
template <>
struct ppdetail<xo::scm::LocalSymtab*> {
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::LocalSymtab* x) {
if (x) {
return x->pretty_print(ppii);
} else {
ppii.pps()->write("<nullptr ");
ppii.pps()->write(reflect::type_name<xo::scm::LocalSymtab>());
ppii.pps()->write(">");
return ppii.pps()->has_margin();
}
}
};
}
}

View file

@ -0,0 +1,27 @@
/* file pretty_variable.hpp
*
* author: Roland Conybeare, Jul 2025
*/
#pragma once
#include "pretty_expression.hpp"
#include "Variable.hpp"
namespace xo {
namespace print {
template <>
struct ppdetail<xo::scm::Variable> {
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::Variable & x) {
return x.pretty_print(ppii);
}
};
template <>
struct ppdetail<xo::scm::Variable *> {
static bool print_pretty(const ppindentinfo & ppii, const xo::scm::Variable * x) {
return x->pretty_print(ppii);
}
};
}
}

View file

@ -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 **/

View file

@ -0,0 +1,58 @@
/** @file type_ref.hpp **/
#pragma once
#include "xo/flatstring/flatstring.hpp"
#include "xo/reflect/TypeDescr.hpp"
namespace xo {
namespace scm {
using prefix_type = xo::flatstring<8>;
using type_var = xo::flatstring<20>;
/** @class type_ref
* @brief name and eventual resolution for type associated with an expression.
*
* Type inference / unification operates on
* @ref xo::scm::TypeBlueprint instances, see also.
**/
class type_ref {
public:
using TypeDescr = xo::reflect::TypeDescr;
public:
type_ref() = default;
type_ref(const type_var& id, TypeDescr td);
/** if type not determined (@p td is nullptr),
* -> generate and store type variable name.
* otherwise type already resolvedn
**/
static type_ref dwim(prefix_type prefix, 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*/
/** end type_ref.hpp **/

View file

@ -0,0 +1,63 @@
/** @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);
/** lookup type variable by @p name, to get resolution **/
rp<TypeBlueprint> lookup(const type_var & name) const;
private:
type_substitution_map constraint_map_;
};
} /*namespace scm*/
} /*namespace xo*/
/** end type_unifier.hpp **/

View file

@ -0,0 +1,203 @@
/* @file Apply.cpp */
#include "Apply.hpp"
#include "PrimitiveExpr.hpp"
#include "exprtype.hpp"
#include "pretty_expression.hpp"
#include "xo/indentlog/print/vector.hpp"
#include "xo/indentlog/print/pretty_vector.hpp"
#include <cstdint>
namespace xo {
namespace scm {
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);
}
// ----- integer comparison -----
rp<Apply>
Apply::make_cmp_eq_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_eq2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_cmp_ne_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_ne2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_cmp_lt_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_lt2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_cmp_le_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_le2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_cmp_gt_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_gt2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_cmp_ge_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_cmp_i64::make_cmp_ge2_i64(),
{lhs, rhs});
}
// ----- integer arithmetic -----
rp<Apply>
Apply::make_add2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_i64::make_add2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_sub2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_i64::make_sub2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_mul2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_i64::make_mul2_i64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_div2_i64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_i64::make_div2_i64(),
{lhs, rhs});
}
// ----- floating point arithmetic -----
rp<Apply>
Apply::make_add2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_f64::make_add2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_sub2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_f64::make_sub2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_mul2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_f64::make_mul2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_div2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(PrimitiveExpr_f64::make_div2_f64(),
{lhs, rhs});
}
void
Apply::attach_envs(bp<SymbolTable> p) {
fn_->attach_envs(p);
for (const auto & arg : argv_)
arg->attach_envs(p);
}
void
Apply::display(std::ostream & os) const {
os << "<Apply"
<< xtag("fn", fn_)
<< xtag("argv", argv_)
<< ">";
}
std::uint32_t
Apply::pretty_print(const ppindentinfo & ppii) const
{
return ppii.pps()->pretty_struct(ppii, "Apply",
refrtag("fn", fn_),
refrtag("argv", argv_));
#ifdef OBSOLETE
ppstate * pps = ppii.pps();
if (ppii.upto()) {
if (!pps->print_upto("<Apply"))
return false;
if (!pps->print_upto_tag("fn", fn_))
return false;
if (!pps->print_upto_tag("argv", argv_))
return false;
return true;
} else {
pps->write("<Apply");
pps->newline_pretty_tag(ppii.ci1(), "fn", fn_);
pps->newline_pretty_tag(ppii.ci1(), "argv", argv_);
pps->write(">");
return false;
}
#endif
}
} /*namespace scm*/
} /*namespace xo*/
/* end Apply.cpp */

View file

@ -0,0 +1,103 @@
/* file AssignExpr.cpp
*
* author: Roland Conybeare
*/
#include "AssignExpr.hpp"
#include "pretty_expression.hpp"
#include "pretty_variable.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <cstdint>
namespace xo {
namespace scm {
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(bp<SymbolTable> p) {
lhs_->attach_envs(p);
rhs_->attach_envs(p);
}
void
AssignExpr::display(std::ostream & os) const {
os << "<Assign"
<< xtag("lhs", lhs_)
<< xtag("rhs", rhs_)
<< ">";
}
std::uint32_t
AssignExpr::pretty_print(const ppindentinfo & ppii) const {
return ppii.pps()->pretty_struct(ppii, "AssignExpr",
refrtag("lhs", lhs_),
refrtag("rhs", rhs_));
}
} /*namespace scm*/
} /*namespace xo*/
/* end AssignExpr.cpp */

View file

@ -0,0 +1,30 @@
# 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
GlobalSymtab.cpp
LocalSymtab.cpp
ConvertExpr.cpp
PrimitiveExpr.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)
# end CMakeLists.txt

View file

@ -0,0 +1,61 @@
/* file ConvertExpr.cpp
*
* author: Roland Conybeare
*/
#include "ConvertExpr.hpp"
#include "pretty_expression.hpp"
namespace xo {
namespace scm {
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_)
<< ">";
}
std::uint32_t
ConvertExpr::pretty_print(const ppindentinfo & ppii) const {
return ppii.pps()->pretty_struct(ppii, "Convert",
rtag("dest_type", print::quot(this->valuetype()->short_name())),
refrtag("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 scm*/
} /*namespace xo*/
/* end ConvertExpr.cpp */

View file

@ -0,0 +1,124 @@
/* file DefineExpr.cpp
*
* author: Roland Conybeare
*/
#include "DefineExpr.hpp"
#include "Variable.hpp"
#include "pretty_expression.hpp"
#include <cstdint>
namespace xo {
namespace scm {
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_var_{Variable::make(lhs_name, rhs_valuetype)},
rhs_{std::move(rhs)}
{
this->free_var_set_ = this->calc_free_variables();
}
const std::string &
DefineExpr::lhs_name() const { return lhs_var_->name(); }
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_var_->name())
<< xtag("rhs", rhs_)
<< ">";
} /*display*/
std::uint32_t
DefineExpr::pretty_print(const ppindentinfo & ppii) const
{
return ppii.pps()->pretty_struct(ppii, "Define",
//refrtag("type", this->valuetype()), // need pretty
refrtag("name", lhs_var_->name()),
refrtag("rhs", rhs_));
}
// ----- 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_lhs_name(const std::string & x)
{
this->lhs_var_->assign_name(x);
}
void
DefineExprAccess::assign_rhs(const rp<Expression> & x)
{
assert(x);
this->rhs_ = x;
if (x) {
if (lhs_var_ && !lhs_var_->valuetype()) {
this->lhs_var_->assign_valuetype(x->valuetype());
}
this->assign_valuetype(x->valuetype());
}
this->free_var_set_ = this->calc_free_variables();
}
} /*namespace scm*/
} /*namespace xo*/
/* end DefineExpr.cpp */

View file

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

View file

@ -0,0 +1,76 @@
/* @file GeneralizedExpression.cpp */
#include "GeneralizedExpression.hpp"
#include "pretty_expression.hpp"
#include <cstdint>
namespace xo {
namespace scm {
namespace {
using xo::scm::prefix_type;
prefix_type exprtype2prefix(exprtype x)
{
switch (x) {
case exprtype::invalid: assert(false); break;
case exprtype::constant: return prefix_type::from_chars("k");
case exprtype::primitive: return prefix_type::from_chars("pm");
case exprtype::define: return prefix_type::from_chars("def");
case exprtype::assign: return prefix_type::from_chars("=");
case exprtype::apply: return prefix_type::from_chars("@");
case exprtype::lambda: return prefix_type::from_chars("lm");
case exprtype::variable: return prefix_type::from_chars("var");
case exprtype::ifexpr: return prefix_type::from_chars("if");
case exprtype::sequence: return prefix_type::from_chars("seq");
case exprtype::convert: return prefix_type::from_chars("cvt");
case exprtype::n_expr: assert(false); break;
}
return prefix_type::from_chars("?expr");
}
}
GeneralizedExpression::GeneralizedExpression(exprtype extype,
TypeDescr valuetype)
: extype_{extype},
valuetype_ref_{type_ref::dwim(exprtype2prefix(extype), valuetype)}
{}
GeneralizedExpression::GeneralizedExpression(exprtype extype,
prefix_type prefix,
TypeDescr valuetype)
: extype_{extype},
valuetype_ref_{type_ref::dwim(prefix, valuetype)}
{}
std::string
GeneralizedExpression::display_string() const {
return tostr(*this);
}
#ifdef SUPERSEDED // currently all derived expression types support pretty printing
std::uint32_t
GeneralizedExpression::pretty_print(const ppindentinfo & ppii) const {
// Slooooow fallback for subtypes that don't implement pretty printing support
// Currently have support for:
// - Variable
// - Lambda
// - DefineExpr
// - Sequence
// - Apply
// - Primitive
// - IfExpr
ppstate * pps = ppii.pps();
std::uint32_t saved = pps->pos();
pps->write(display_string());
if (ppii.upto() && !pps->has_margin())
return false;
return ppii.upto() ? pps->scan_no_newline(saved) : true;
}
#endif
} /*namespace scm*/
} /*namespace xo*/
/* end GeneralizedExpression.cpp */

View file

@ -0,0 +1,61 @@
/* file GlobalEnv.cpp
*
* author: Roland Conybeare, Jul 2025
*/
#include "xo/indentlog/print/ppdetail_atomic.hpp"
#include "GlobalSymtab.hpp"
#include "Expression.hpp"
namespace xo {
namespace scm {
GlobalSymtab::GlobalSymtab() = default;
bp<Expression>
GlobalSymtab::require_global(const std::string & vname,
bp<Expression> expr)
{
this->global_map_[vname] = expr.get();
return expr;
} /*require_global*/
void
GlobalSymtab::upsert_local(bp<Variable> target) {
// in practice: paraphrase of .require_global()
this->global_map_[target->name()] = target.promote();
}
void
GlobalSymtab::print(std::ostream & os) const {
os << "<GlobalEnv"
<< xtag("size", global_map_.size())
<< ">";
}
std::uint32_t
GlobalSymtab::pretty_print(const xo::print::ppindentinfo & ppii) const
{
using xo::print::ppstate;
ppstate * pps = ppii.pps();
if (ppii.upto()) {
if (!pps->print_upto("<GlobalEnv"))
return false;
if (!pps->print_upto_tag("size", global_map_.size()))
return false;
pps->write(">");
return true;
} else {
pps->write("<GlobalEnv");
pps->newline_pretty_tag(ppii.ci1(), "size", global_map_.size());
pps->write(">");
return false;
}
}
} /*namespace scm*/
} /*namespace xo*/

View file

@ -0,0 +1,102 @@
/* @file IfExpr.cpp */
#include "IfExpr.hpp"
#include "pretty_expression.hpp"
#include "pretty_variable.hpp"
//#include "xo/indentlog/print/vector.hpp"
namespace xo {
namespace scm {
auto IfExpr::check_consistent_valuetype(const rp<Expression> & when_true,
const rp<Expression> & when_false) -> TypeDescr
{
if (when_true->valuetype() != when_false->valuetype())
return nullptr;
return when_true->valuetype();
}
void IfExpr::establish_valuetype()
{
if (this->when_true_.get() && this->when_false_.get())
this->assign_valuetype(check_consistent_valuetype(this->when_true_, this->when_false_));
}
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_);
if (when_false_)
os << xtag("when_false", when_false_);
os << ">";
} /*display*/
std::uint32_t
IfExpr::pretty_print(const ppindentinfo & ppii) const {
return ppii.pps()->pretty_struct(ppii, "IfExpr",
refrtag("test", test_),
refrtag("when_true", when_true_),
refrtag("when_false", when_false_));
}
rp<IfExprAccess>
IfExprAccess::make(rp<Expression> test,
rp<Expression> when_true,
rp<Expression> when_false)
{
auto ifexpr_type = check_consistent_valuetype(when_true, when_false);
return new IfExprAccess(ifexpr_type, std::move(test), std::move(when_true), std::move(when_false));
}
rp<IfExprAccess>
IfExprAccess::make_empty()
{
return new IfExprAccess(nullptr /*ifexpr_valuetype*/,
nullptr /*test*/,
nullptr /*when_true*/,
nullptr /*when_false*/);
}
void
IfExprAccess::assign_when_true(rp<Expression> x)
{
this->when_true_ = std::move(x);
}
void
IfExprAccess::assign_when_false(rp<Expression> x)
{
this->when_false_ = std::move(x);
}
} /*namespace scm*/
} /*namespace xo*/
/* end IfExpr.cpp */

View file

@ -0,0 +1,410 @@
/* @file Lambda.cpp */
#include "Lambda.hpp"
#include "exprtype.hpp"
#include "pretty_expression.hpp"
#include "pretty_variable.hpp"
#include "xo/reflect/TypeDescr.hpp"
#include "xo/reflect/function/FunctionTdx.hpp"
#include "xo/indentlog/print/vector.hpp"
#include "xo/indentlog/print/pretty_vector.hpp"
#include <map>
#include <sstream>
namespace xo {
using xo::reflect::TypeDescr;
using xo::reflect::TypeDescrBase;
using xo::reflect::FunctionTdxInfo;
using std::stringstream;
namespace scm {
TypeDescr
Lambda::assemble_lambda_td(const std::vector<rp<Variable>> & argv,
TypeDescr return_td)
{
assert(return_td != nullptr);
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(return_td,
arg_td_v,
false /*!is_noexcept*/);
TypeDescr lambda_td
= TypeDescrBase::require_by_fn_info(function_info);
return lambda_td;
}
TypeDescr
Lambda::assemble_lambda_td(const std::vector<rp<Variable>> & argv,
TypeDescr explicit_return_td,
const rp<Expression> & body)
{
if (!body)
return nullptr;
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())
TypeDescr return_td = explicit_return_td ? explicit_return_td : body->valuetype();
return assemble_lambda_td(argv, return_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,
TypeDescr lambda_td,
const rp<LocalSymtab> & env,
const rp<Expression> & body)
{
return new Lambda(name, lambda_td, env, body);
}
rp<Lambda>
Lambda::make_from_env(const std::string & name,
const rp<LocalSymtab> & env,
TypeDescr explicit_return_td,
const rp<Expression> & body)
{
TypeDescr lambda_td = assemble_lambda_td(env->argv(), explicit_return_td, 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;
}
rp<Lambda>
Lambda::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body,
const rp<SymbolTable> & parent_env)
{
rp<LocalSymtab> env = LocalSymtab::make(argv, parent_env);
TypeDescr explicit_return_td = nullptr;
return make_from_env(name, env, explicit_return_td, body);
} /*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](bp<Expression> x) -> rp<Expression>
{
if (x->extype() == exprtype::variable) {
bp<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 explicit_return_td = nullptr;
TypeDescr lambda_td
= assemble_lambda_td(this->local_env_->argv(), explicit_return_td, 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, bp<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(bp<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
bp<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<LocalSymtab> & local_env,
const rp<Expression> & body)
: ProcedureExprInterface(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, bp<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(bp<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
bp<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(bp<SymbolTable> 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*/
std::uint32_t
Lambda::pretty_print(const ppindentinfo & ppii) const
{
return ppii.pps()->pretty_struct(ppii, "Lambda",
refrtag("name", name_),
refrtag("argv", local_env_->argv()),
refrtag("body", body_));
}
// ----- Lambda Access -----
rp<LambdaAccess>
LambdaAccess::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body,
const rp<SymbolTable> & parent_env)
{
TypeDescr explicit_return_td = nullptr;
TypeDescr lambda_td = assemble_lambda_td(argv, explicit_return_td, body);
rp<LocalSymtab> env = LocalSymtab::make(argv, parent_env);
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<LocalSymtab> & 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 scm*/
} /*namespace xo*/
/* end Lambda.cpp */

View file

@ -0,0 +1,135 @@
/* file LocalSymtab.cpp
*
* author: Roland Conybeare
*/
#include "LocalSymtab.hpp"
#include "pretty_variable.hpp"
#include "xo/indentlog/print/pretty_vector.hpp"
#include "xo/indentlog/print/vector.hpp"
namespace xo {
namespace scm {
rp<LocalSymtab>
LocalSymtab::make_empty() {
return new LocalSymtab(std::vector<rp<Variable>>(), nullptr);
}
rp<LocalSymtab>
LocalSymtab::make(const std::vector<rp<Variable>> & argv,
const rp<SymbolTable> & parent_env)
{
return new LocalSymtab(argv, parent_env);
}
rp<LocalSymtab>
LocalSymtab::make1(const rp<Variable> & arg1,
const rp<SymbolTable> & parent_env)
{
std::vector<rp<Variable>> argv = { arg1 };
return make(argv, parent_env);
}
LocalSymtab::LocalSymtab(const std::vector<rp<Variable>> & argv,
const rp<SymbolTable> & parent_env)
: origin_{nullptr},
argv_(argv),
parent_env_{parent_env}
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag), xtag("this", (void*)this), xtag("argv", argv_));
}
binding_path
LocalSymtab::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
LocalSymtab::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*/
void
LocalSymtab::assign_parent(bp<SymbolTable> p) {
if ((parent_env_.get() != nullptr) && (parent_env_.get() != p.get())) {
throw std::runtime_error(tostr("LocalSymtab::assign_parent(P2): already have established parent P1",
xtag("P1", parent_env_),
xtag("P2", p)));
assert(false);
}
parent_env_ = p.promote();
}
void
LocalSymtab::upsert_local(bp<Variable> target) {
for (auto & var : this->argv_) {
if (var->name() == target->name()) {
/* replace existing variable. This may change its type */
var = target.promote();
return;
}
}
/* control here: target not already present in this frame -> append */
this->argv_.push_back(target.promote());
}
void
LocalSymtab::print(std::ostream& os) const {
os << "<LocalSymtab"
<< xtag("argv", argv_)
<< ">";
}
std::uint32_t
LocalSymtab::pretty_print(const xo::print::ppindentinfo & ppii) const {
using xo::print::ppstate;
ppstate * pps = ppii.pps();
if (ppii.upto()) {
if (!pps->print_upto("<LocalSymtab"))
return false;
if (!pps->print_upto_tag("argv", argv_))
return false;
pps->write(">");
return true;
} else {
pps->write("<LocalSymtab");
pps->newline_pretty_tag(ppii.ci1(), "this", (void*)this);
pps->newline_pretty_tag(ppii.ci1(), "argv", argv_);
pps->write(">");
return false;
}
}
} /*namespace scm*/
} /*namespace xo*/
/* end LocalSymtab.cpp */

View file

@ -0,0 +1,296 @@
/* @file PrimitiveExpr.cpp */
#include "PrimitiveExpr.hpp"
#include <cstdint>
extern "C" {
/** code here is used in two context:
* 1. Fallback implementation under llvm.
* In practice will use llvm intrinsic instead.
* See xo-jit/src/jit/MachPipeline.cpp
* 2. Schematika interpreter (aspirational asof jul 2025, wip nov 2025)
* For schematika interpreter need to uplift
* these to obj::Primitive.
* See BuiltinPrimitives::install_interpreter_conversions()
* in xo-interpreter/src/BuiltinPrimitives.cpp
**/
bool
cmp_eq2_i64(std::int64_t x, std::int64_t y) {
return x == y;
}
bool
cmp_ne2_i64(std::int64_t x, std::int64_t y) {
return x != y;
}
bool
cmp_lt2_i64(std::int64_t x, std::int64_t y) {
return x < y;
}
bool
cmp_le2_i64(std::int64_t x, std::int64_t y) {
return x <= y;
}
bool
cmp_gt2_i64(std::int64_t x, std::int64_t y) {
return x > y;
}
bool
cmp_ge2_i64(std::int64_t x, std::int64_t y) {
return x >= y;
}
std::int64_t
add2_i64(std::int64_t x, std::int64_t y) {
return x + y;
}
std::int64_t
sub2_i64(std::int64_t x, std::int64_t y) {
return x - y;
}
std::int64_t
mul2_i64(std::int64_t x, std::int64_t y) {
return x * y;
}
std::int64_t
div2_i64(std::int64_t x, std::int64_t y) {
return x / y;
}
// ----------------------------------------------------------------
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 scm {
auto
PrimitiveExpr_cmp_i64::make_cmp_eq2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_eq2_i64",
&cmp_eq2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_eq);
return s_retval;
}
auto
PrimitiveExpr_cmp_i64::make_cmp_ne2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_ne2_i64",
&cmp_ne2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_ne);
return s_retval;
}
auto
PrimitiveExpr_cmp_i64::make_cmp_lt2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_lt2_i64",
&cmp_lt2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_slt);
return s_retval;
}
auto
PrimitiveExpr_cmp_i64::make_cmp_le2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_le2_i64",
&cmp_le2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_sle);
return s_retval;
}
auto
PrimitiveExpr_cmp_i64::make_cmp_gt2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_gt2_i64",
&cmp_gt2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_sgt);
return s_retval;
}
auto
PrimitiveExpr_cmp_i64::make_cmp_ge2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@cmp_ge2_i64",
&cmp_ge2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_sge);
return s_retval;
}
/* TODO: remaining integer arithmetic */
auto
PrimitiveExpr_i64::make_add2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@add2_i64",
&add2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_add);
return s_retval;
}
auto
PrimitiveExpr_i64::make_sub2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@sub2_i64",
&sub2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_sub);
return s_retval;
}
auto
PrimitiveExpr_i64::make_mul2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@mul2_i64",
&mul2_i64,
true /*explicit_symbol_def*/,
llvmintrinsic::i_mul);
return s_retval;
}
auto
PrimitiveExpr_i64::make_div2_i64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@div2_i64",
&div2_i64,
true /*explicit_symbol+def*/,
llvmintrinsic::i_sdiv);
return s_retval;
}
// ----- floating-point arithmetic -----
auto
PrimitiveExpr_f64::make_add2_f64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@add2_f64",
&add2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_add);
return s_retval;
}
auto
PrimitiveExpr_f64::make_sub2_f64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@sub2_f64",
&sub2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_sub);
return s_retval;
}
auto
PrimitiveExpr_f64::make_mul2_f64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@mul2_f64",
&mul2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_mul);
return s_retval;
}
auto
PrimitiveExpr_f64::make_div2_f64() -> rp<PrimitiveExprType>
{
static rp<PrimitiveExprType> s_retval;
if (!s_retval)
s_retval = PrimitiveExpr::make("@div2_f64",
&div2_f64,
true /*explicit_symbol_def*/,
llvmintrinsic::fp_div);
return s_retval;
}
} /*namespace scm*/
} /*namespace xo*/
/* end PrimitiveExpr.cpp */

View file

@ -0,0 +1,122 @@
/* @file Sequence.cpp */
#include "Sequence.hpp"
#include "pretty_expression.hpp"
#include <cstddef>
namespace xo {
namespace scm {
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(bp<SymbolTable> 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 << ">";
}
std::uint32_t
Sequence::pretty_print(const ppindentinfo & ppii) const
{
ppstate * pps = ppii.pps();
if (ppii.upto()) {
if (!pps->print_upto("<Sequence"))
return false;
std::size_t i = 0;
for (const auto & expr_i : expr_v_) {
if (!pps->has_margin())
return false;
std::string i_str = tostr("[", i, "]");
if (!pps->print_upto_tag(i_str.c_str(), expr_i))
return false;
++i;
}
if (!pps->has_margin())
return false;
pps->write(">");
return true;
} else {
pps->write("<Sequence");
std::size_t i = 0;
for (const auto & expr_i : expr_v_) {
std::string i_str = tostr("[", i, "]");
pps->newline_pretty_tag(ppii.ci1(),
i_str.c_str(),
expr_i);
++i;
}
pps->write(">");
return false;
}
}
} /*namespace scm*/
} /*namespace xo*/
/* end Sequence.cpp */

View file

@ -0,0 +1,57 @@
/* @file Variable.cpp */
#include "Variable.hpp"
#include "SymbolTable.hpp"
#include "pretty_expression.hpp"
namespace xo {
namespace scm {
std::string
Variable::gensym(const std::string & prefix) {
static std::size_t s_counter = 0;
++s_counter;
char buf[32];
snprintf(buf, sizeof(buf), "%ld", s_counter);
return prefix + std::string(buf);
}
void
Variable::attach_envs(bp<SymbolTable> 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*/
std::uint32_t
Variable::pretty_print(const ppindentinfo & ppii) const {
/* 1. rtag instead of refrtag:
* print::quot() is a temporary rvalue; lifetime ends before control enters pretty_struct()
*/
return ppii.pps()->pretty_struct(ppii, "Variable",
refrtag("name", name_),
rtag("type", print::unq(this->valuetype()
? this->valuetype()->short_name()
: "nullptr")));
}
} /*namespace scm*/
} /*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 */

View 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 **/

View file

@ -0,0 +1,61 @@
/** @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; }
type_ref
type_ref::dwim(prefix_type prefix, TypeDescr td)
{
if (td) {
/** type resolved, type variable not needed **/
return type_ref(type_var(), td);
} else {
/** type not resolved, assign a unique type variable **/
return type_ref(generate_unique(prefix), td);
}
}
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);
(void)n;
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 **/

View file

@ -0,0 +1,230 @@
/** @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))
};
}
rp<TypeBlueprint>
type_unifier::lookup(const type_var & name) const
{
auto ix = constraint_map_.find(name);
if (ix != constraint_map_.end()) {
return ix->second;
} else {
return nullptr;
}
}
} /*namespace scm*/
} /*namespace xo*/
/** end type_unifier.cpp **/

View 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)

View file

@ -0,0 +1,6 @@
/* file expression_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/* end expression_utest_main.cpp */

View file

@ -0,0 +1,247 @@
/* @file expression.text.cpp
*
* 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,
#ifdef __APPLE__
.expect_concrete_typename_ = "long long",
#else
.expect_concrete_typename_ = "long int",
#endif
.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,
#ifdef __APPLE__
.expect_concrete_typename_ = "long long",
#else
.expect_concrete_typename_ = "long int",
#endif
.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*/