.xo-interpreter subrepo tidy

This commit is contained in:
Roland Conybeare 2026-06-06 22:12:50 -04:00
commit d11f6ca597
38 changed files with 0 additions and 3210 deletions

View file

@ -1,41 +0,0 @@
# xo-interpreter/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project (xo_interpreter 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})
# ----------------------------------------------------------------
# note on ordering: must read .cmake defn of lib before configuring any examples
add_subdirectory(src/interpreter)
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
add_subdirectory(example)
add_subdirectory(utest)
# ----------------------------------------------------------------
#if (XO_ENABLE_EXAMPLES)
# install(TARGETS xo_interpreter_ex1 DESTINATION bin/xo/example)
#endif()
# ----------------------------------------------------------------
# docs targets depend on all other library/utest targets
#
#add_subdirectory(docs)
# end CMakeLists.txt

View file

@ -1 +0,0 @@
# xo-interpreter

View file

@ -1,41 +0,0 @@
# ----------------------------------------------------------------
# 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 (XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# local version of xo-cmake macros
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/xo-cmake/cmake")
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
else()
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

@ -1,8 +0,0 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(xo_alloc)
#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

@ -1,9 +0,0 @@
# xo-alloc/docs/CMakeLists.txt
xo_doxygen_collect_deps()
xo_docdir_doxygen_config()
xo_docdir_sphinx_config(
index.rst install.rst)
# see xo-reader/doc or xo-unit/doc for working examples
# example.rst install.rst implementation.rst

View file

@ -1,41 +0,0 @@
standalone build
+-----------------------------------------------+
| cmake |
| CMakeLists.txt |
| $PREFIX/share/cmake/xo_macros/xo_cxx.cmake |
+-----------------------------------------------+
|
| +----------------------+
+------------------------------------------------->| .build/docs/Doxyfile |
| +----------------------+
| ^
| (cmake) |
| /------------/
| |
| +---------------------------------------+ +-----------------+
+---->| doxygen |--------->| .build/docs/dox |
| | $PREFIX/share/xo-macros/Doxyfile.in | (doxygen)| +- html/ |
| +---------------------------------------+ | +- xml/ |
| +-----------------+
| |
| |(sphinx)
| |
| v
| +---------------------------------------+ +--------------------+
\---->| sphinx |------->| .build/docs/sphinx |
| +- conf.py | | +- html/ |
| +- _static/ | +--------------------+
| +- *.rst |
+---------------------------------------+
umbrella build relies on top-level cmake macros
files
README this file
CMakeLists.txt build entry point
conf.py sphinx config
_static static files for sphinx
index.rst toplevel sphinx document; entry point

View file

@ -1 +0,0 @@
add any static {.html, .js, ..} files for sphinx to pickup here

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

View file

@ -1,39 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'xo interpreter documentation'
copyright = '2025, Roland Conybeare'
author = 'Roland Conybeare'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
#extensions = []
extensions = [ "breathe",
"sphinx.ext.mathjax", # inline math
"sphinx.ext.autodoc", # generate info from docstrings
"sphinxcontrib.ditaa", # diagrams-through-ascii-art
"sphinxcontrib.plantuml" # text -> uml diagrams
]
# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in
# match project name in Doxyfile.in
breathe_default_project = "xodoxxml"
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
#html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_favicon = '_static/img/favicon.ico'

View file

@ -1,12 +0,0 @@
.. xo-interpreter documentation master file.
xo-interpreter documentation
============================
xo-interpreter provides an interpreter for the Schematika language
.. toctree::
:maxdepth: 2
:caption: xo-interpreter contents
install

View file

@ -1,202 +0,0 @@
.. _install:
.. toctree:
:maxdepth: 2
Source
======
Source code lives on github `here`_
.. _here: https://github.com/rconybea/xo-interpreter
To clone from git:
.. code-block:: bash
git clone https://github.com/rconybea/xo-interpreter
Tested with gcc 14.2
Install
=======
One-step Install
----------------
Install xo-interpreter along with the rest of *XO* from `xo-umbrella2 source`_:
see install instructions for xo-umbrella2.
.. _xo-umbrella2 source: https://github.com/rconybea/xo-umbrella2
Essential Xo Dependencies
-------------------------
``xo-interpreter`` uses several supporting libraries from elsewhere in the *XO* project:
- `xo-reader source`_ (Schematika expression parser)
- `xo-expression source`_ (Schematika AST representation)
- `xo-tokenizer source`_ (Schematika lexer)
- `xo-object source`_ (gc-eligible runtime polymorphism)
- `xo-randomgen source`_ (fast pseudo-random number generators)
- `xo-alloc source`_ (arena allocators, garbage collector)
- `xo-unit source`_ (dimension checking library)
- `xo-ratio source`_ (exact ratio library)
- `xo-flatstring source`_ (no-allocation string library)
- `xo-callback source`_ (callback library)
- `xo-reflectutil source`_ (reflection utils for participating libs)
- `xo-reflect source`_ (reflection library)
- `xo-refcnt source`_ (reference-counting library)
- `xo-subsys source`_ (utility library)
- `xo-indentlog source`_ (structured logging, pretty-printing)
- `xo-cmake source`_ (shared cmake macros)
.. _xo-reader source: https://github.com/rconybea/xo-reader
.. _xo-expression source: https://github.com/rconybea/xo-expression
.. _xo-tokenizer source: https://github.com/rconybea/xo-tokenizer
.. _xo-object source: https://github.com/rconybea/xo-object
.. _xo-randomgen source: https://github.com/rconybea/xo-randomgen
.. _xo-alloc source: https://github.com/rconybea/xo-alloc
.. _xo-unit source: https://github.com/rconybea/xo-unit
.. _xo-ratio source: https://github.com/rconybea/xo-ratio
.. _xo-flatstring source: https://github.comr/rconybea/xo-flatstring
.. _xo-callback source: https://github.com/rconybea/xo-callback
.. _xo-reflect source: https://github.com/rconybea/xo-reflect
.. _xo-refcnt source: https://github.com/rconybea/refcnt
.. _xo-subsys source: https://github.com/rconybea/subsys
.. _xo-indentlog source: https://github.com/rconybea/indentlog
.. _xo-cmake source: https://github.com/rconybea/xo-cmake
Building from source
--------------------
Instructions for building xo-interpreter from source, along with only its essential dependencies.
Install scripts for XO libraries depend on helper scripts installed from `xo-cmake`.
Preamble:
.. code-block:: bash
mkdir -p ~/proj/xo
cd ~/proj/xo
git clone https://github.com/rconybea/xo-cmake
PREFIX=$HOME/local # or desired installation path
# will want PREFIX/bin in PATH to use xo-cmake helpers
PATH=$PREFIX/bin:$PATH
Isntall `xo-cmake`:
.. code-block:: bash
cmake -B xo-cmake/.build -S xo-cmake
cmake --install xo-cmake/.build
Now that we have xo-build in PATH, can build+install XO components in topological order:
.. code-block:: bash
xo-build --clone --configure --build --install xo-indentlog
xo-build --clone --configure --build --install xo-subsys
xo-build --clone --configure --build --install xo-refcnt
xo-build --clone --configure --build --install xo-reflect
xo-build --clone --configure --build --install xo-reflectutil
xo-build --clone --configure --build --install xo-callback
xo-build --clone --configure --build --install xo-flatstring
xo-build --clone --configure --build --install xo-ratio
xo-build --clone --configure --build --install xo-unit
xo-build --clone --configure --build --install xo-alloc
xo-build --clone --configure --build --install xo-randomgen
xo-build --clone --configure --build --install xo-object
xo-build --clone --configure --build --install xo-tokenizer
xo-build --clone --configure --build --install xo-expression
xo-build --clone --configure --build --install xo-reader
Directories under ``PREFIX`` will then contain something like:
.. code-block::
PREFIX
+= bin
| +- xo-build
│ +- xo-cmake-config
│ \- xo-cmake-lcov-harness
+─ include
| \- xo
│ +- alloc/
| +- callback/
| +- cxxutil/
| +- expression/
| | +- typeinf/
| | ..
| +- flatstring/
| +- indentlog/
| | +- machdep/
| | +- print/
| | +- timeutil/
| | ..
| +- object/
| +- randomgen/
| +- ratio/
| +- reader/
| +- refcnt/
| +- reflect/
| | +- atomic/
| | +- function/
| | +- pointer/
| | +- struct/
| | +- vector/
| | ..
| +- reflectutil/
| +- subsys/
| +- tokenizer/
| \- unit/
+- lib
| +- cmake
| | +- callback/
| | +- indentlog/
| | +- randomgen/
| | +- refcnt/
| | +- reflect/
| | +- subsys/
| | +- xo_alloc/
| | +- xo_expression/
| | +- xo_flatstring/
| | +- xo_object/
| | +- xo_ratio/
| | +- xo_reader/
| | +- xo_reflectutil/
| | +- xo_tokenizer/
| | \- xo_unit/
| +- librefcnt.so
| ..
\- share
+- cmake
| \- xo-macros
| +- code-coverage.cmake
| +- xo_cxx.cmake
| \- xo-project-macros.cmake
+- etc
| \- xo
| \- subsystem_list
+- xo-macros
+- Doxyfile.in
+- gen-ccov.in
\- xo-bootstrap-macros.cmake
CMake Support
-------------
To use built-in cmake support, when using ``xo-interpreter`` from another project:
Make sure ``PREFIX/lib/cmake`` is searched by cmake (for example include it in ``CMAKE_PREFIX_PATH``)
Add to your ``CMakeLists.txt``:
.. code-block:: cmake
FindPackage(xo_interpreter CONFIG REQUIRED)
target_link_libraries(mytarget PUBLIC xo_interpreter)

View file

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

View file

@ -1,17 +0,0 @@
# xo-interpreter/example/replxx/CMakeLists.txt
set(SELF_EXE xo_interpreter_replxx)
set(SELF_SRCS replxx.cpp)
if (XO_ENABLE_EXAMPLES)
xo_add_executable(${SELF_EXE} ${SELF_SRCS})
xo_self_dependency(${SELF_EXE} xo_interpreter)
# TODO: consider promoting to regular app
xo_dependency(${SELF_EXE} xo_reader)
xo_external_target_dependency(${SELF_EXE} replxx replxx::replxx)
find_package(Threads REQUIRED)
target_link_libraries(${SELF_EXE} PUBLIC Threads::Threads)
endif()
# end CMakeLists.txt

View file

@ -1,19 +0,0 @@
/** @file replxx.cpp **/
#include "xo/interpreter/Schematika.hpp"
int
main(int argc, char ** argv)
{
using xo::log_level;
using xo::scm::Schematika;
Schematika::Config cfg;
cfg.debug_flag = true;
cfg.vsm_log_level_ = log_level::verbose;
Schematika scm = Schematika::make(cfg);
scm.interactive_repl();
}
/* end replxx.cpp */

View file

@ -1,36 +0,0 @@
/** @file BuiltinPrimitives.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "xo/object/ObjectConverter.hpp"
#include "xo/allocutil/IAlloc.hpp"
#include "Primitive.hpp"
#include "GlobalEnv.hpp"
namespace xo {
namespace scm {
struct BuiltinPrimitives {
public:
using ObjectConverter = xo::obj::ObjectConverter;
template <typename Expr>
static void install_pm(gc::IAlloc * mm, rp<Expr> pm_expr, gp<GlobalEnv> env) {
gp<Object> rhs
= xo::obj::make_primitive(mm, pm_expr->name(), pm_expr->value());
/* store in env using this variable-expr */
rp<Variable> lhs
= Variable::make(pm_expr->name(), pm_expr->value_td());
gp<Object> * addr = env->establish_var(lhs.borrow());
*addr = rhs;
}
static void install(gc::IAlloc * mm, gp<GlobalEnv> env);
};
}
}
/* end BuiltinPrimitives.hpp */

View file

@ -1,47 +0,0 @@
/** @file Env.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include "xo/alloc/Object.hpp"
#include "xo/refcnt/Refcounted.hpp"
namespace xo {
namespace scm {
class Variable; // see xo::scm::Variable in xo/expression/Variable.hpp
/** @class Env
* @brief runtime environment, holding variable bindings for schematika interpreter
*
* Garbage-collected
*
* TODO: rename xo-expression xo::scm::Environment -> xo::scm::SymbolTable
**/
class Env : public Object {
public:
/** true iff @p vname is present in Symtab for innermost environment **/
virtual bool local_contains_var(const std::string & vname) const = 0;
/** Fetch storage location for innermost binding of variable with name @p vname.
* nullptr if not found
**/
virtual gp<Object> * lookup_slot(const std::string & vname) = 0;
/** require storage for variable @p v.
* will also establish binding path.
*
* Intended for introducing a new variable,
* replacing any previous variable with the same name.
*
* Beware of invalidating type correctness
*
* @return slot address for runtime value of @p v
**/
virtual gp<Object> * establish_var(bp<Variable> v) = 0;
//gp<Object> lookup_symbol(const std::string & name) const;
};
} /*namespace scm*/
} /*namespace xo*/

View file

@ -1,48 +0,0 @@
/** @file ExpressionBoxed.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include "xo/alloc/Object.hpp"
#include "xo/expression/Expression.hpp"
namespace xo {
namespace scm {
/** @class ExpressionBoxed
* @brief xo::scm::Expression, adapted to xo::Object interface
**/
class ExpressionBoxed : public Object {
public:
explicit ExpressionBoxed(bp<Expression> c);
/** create boxed version of @p c, using allocator @p mm **/
static gp<ExpressionBoxed> make(gc::IAlloc * mm,
bp<Expression> c);
/** runtime downcast **/
static gp<ExpressionBoxed> from(gp<Object> x) {
return gp<ExpressionBoxed>::from(x);
}
const rp<Expression> & contents() const { return contents_; }
// inherited from Object
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;
virtual std::size_t _shallow_size() const final override;
virtual Object * _shallow_copy(gc::IAlloc * mm) const final override;
virtual std::size_t _forward_children(gc::IAlloc * /*gc*/) final override;
private:
/** reference-counted Expression pointer
*
* NOTE correctness requires finalization support in xo::gc::GC
**/
rp<Expression> contents_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end ExpressionBoxed.hpp */

View file

@ -1,73 +0,0 @@
/** @file GlobalEnv.hpp **/
#pragma once
#include "Env.hpp"
#include "xo/allocutil/IAlloc.hpp"
#include "xo/expression/GlobalSymtab.hpp"
namespace xo {
namespace scm {
/** @class GlobalEnv
* @brief Top-level global environment
**/
class GlobalEnv : public Env {
public:
using map_type = std::map<std::string, gp<Object>>;
public:
/** Create top-level global environment, allocating via @p mm.
* Expect one of these per interpreter session.
**/
static gp<GlobalEnv> make_empty(gc::IAlloc * mm,
const rp<GlobalSymtab> & symtab);
#ifdef NOT_USING
gc::IAlloc * get_mm() const { return mm_; }
#endif
const rp<GlobalSymtab> & symtab() const { return symtab_; }
// inherited from Env..
virtual bool local_contains_var(const std::string & vname) const final override;
virtual gp<Object> * lookup_slot(const std::string & vname) final override;
virtual gp<Object> * establish_var(bp<Variable> var) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;
virtual std::size_t _shallow_size() const final override;
virtual Object * _shallow_copy(gc::IAlloc * mm) const final override;
virtual std::size_t _forward_children(gc::IAlloc * mm) final override;
private:
GlobalEnv(const GlobalEnv & x);
GlobalEnv(gc::IAlloc * mm, const rp<GlobalSymtab> & symtab);
private:
/** memory manager to use **/
gc::IAlloc * mm_ = nullptr;
/** global symbol table.
* variables known to @c symtab_ are represented by
* corresponding values in @p slot_map_
**/
rp<GlobalSymtab> symtab_;
/** environment contents.
* expression @c symtab_->lookup_binding(vname)
* has associated value @c slot_map_.at(vname)
*
* TODO: replace with something subject to GC ?
* every member of @ref slot_map_ will have to be a
* GC root
*
* TODO: probably want to hash here instead.
* May also want lhs names to be separately hashed symbols
**/
up<map_type> slot_map_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end GlobalEnv.hpp */

View file

@ -1,95 +0,0 @@
/** @file LocalEnv.hpp **/
#include "Env.hpp"
#include "CVector.hpp"
#include "xo/allocutil/IAlloc.hpp"
#include "xo/expression/LocalSymtab.hpp"
#include <cstddef>
#include <cstdint>
namespace xo {
namespace scm {
/** @class LocalEnv
* @brief Represent a single runtime stack frame for a Schematika function
*
* LocalEnv intended to be used for interpreted functions.
*
* Compiled functions will still likely have stack frames, but need not use the
* @ref LocalEnv class
*
* memory layout:
* ^
* +-----------------------+ |
* | vtable | |
* +-----------------------+ |
* | .parent +------/
* +------------+----------+
* | .slot_v_ | .n_ |
* | +----------+
* | | .v_ +------\
* +------------+----------+ <--/
* | .v_[0] +---------> Object(1)
* +-----------------------+
* . .. .
* +-----------------------+
* | .v_[.n_-1] +---------> Object(n)
* +-----------------------+
**/
class LocalEnv : public Env {
public:
using TaggedPtr = xo::reflect::TaggedPtr;
public:
LocalEnv(gc::IAlloc * mm, gp<LocalEnv> p, const rp<LocalSymtab> & s, std::size_t n);
/** create frame using allocator @p mm,
* with parent @p p and exactly @p n_slot object pointers.
* variable types are taken from symbol table @p s.
**/
static gp<LocalEnv> make(gc::IAlloc * mm,
gp<LocalEnv> p,
const rp<LocalSymtab> & s,
std::size_t n_slot);
/** reflect LocalEnv object representation **/
static void reflect_self();
gp<LocalEnv> parent() const { return parent_; }
std::size_t size() const { return slot_v_.size(); }
gp<Object> operator[](std::size_t i) const { return slot_v_[i]; }
gp<Object> & operator[](std::size_t i) { return slot_v_[i]; }
// inherited from Env..
virtual bool local_contains_var(const std::string & vname) const final override;
virtual gp<Object> * lookup_slot(const std::string & vname) final override;
/** LocalEnv policy is that variable can be established once only.
* For example function arguments must all have distinct names.
**/
virtual gp<Object> * establish_var(bp<Variable> v) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;
virtual std::size_t _shallow_size() const final override;
virtual Object * _shallow_copy(gc::IAlloc * mm) const final override;
virtual std::size_t _forward_children(gc::IAlloc * /*gc*/) final override;
private:
/** parent stack frame **/
gp<LocalEnv> parent_;
/** origin symbol table. records variable names and bindings.
* for a binding path p with leaf slot index j = p.j_slot_:
* @c slot_v_[j] holds value associated with variable @c symtab_->argv_[j]
**/
rp<LocalSymtab> symtab_;
/** environment contents **/
obj::CVector<gp<Object>> slot_v_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end LocalEnv.hpp */

View file

@ -1,70 +0,0 @@
/** @file Schematika.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include "xo/alloc/GC.hpp"
namespace xo {
namespace scm {
/** @class Schematika
* @brief schematika interpreter state
**/
class Schematika {
public:
class Impl;
struct Config {
/** true to enable welcome message **/
bool welcome_flag_ = true;
/** number of command history items to preserve **/
std::size_t history_size = 100;
/** on startup: load command history from this file;
persist last @ref history_size commands to the same file
**/
std::string history_file = "scm_history.txt";
/** when true enable console logging for repl internals **/
bool debug_flag = false;
/** garbage collector configuration **/
gc::Config gc_config_;
/** control schematika vsm logging **/
log_level vsm_log_level_;
};
using IAlloc = xo::gc::IAlloc;
public:
~Schematika();
/** create instance with configuration @p cfg **/
static Schematika make(const Config & cfg);
/** interactive read-eval-print loop.
* Uses replxx to read from stdin.
* If stdin is interactive, accepts line editing commands:
* - ctrl-a goto beginning of line
* - ctrl-e goto end of line
* - ctrl-k delete to end of line
* - meta-<bs> backwards delete word
* - meta-p|<up> retrieve previous command from history
* - meta-n|<down> retrieve next command from history
* - <pgup>/<pgdown> page through history faster
* - ctrl-s forward history search
* - ctrl-r backward history search
**/
void interactive_repl();
private:
explicit Schematika(const Config & cfg);
private:
up<Impl> p_impl_;
};
}
}
/* end Schematika.hpp */

View file

@ -1,28 +0,0 @@
/** @file SchematikaError.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <string>
namespace xo {
namespace scm {
class SchematikaError {
public:
SchematikaError() = default;
explicit SchematikaError(std::string x) : what_{std::move(x)} {}
const std::string & what() const { return what_; }
bool is_error() const { return !what_.empty(); }
bool is_not_an_error() const { return what_.empty(); }
private:
std::string what_;
};
}
}
/* end SchematikaError.hpp */

View file

@ -1,187 +0,0 @@
/** @file VirtualSchematikaMachine.hpp **/
#pragma once
#include "VsmInstr.hpp"
#include "VsmStackFrame.hpp"
#include "SchematikaError.hpp"
#include "GlobalEnv.hpp"
#include "xo/expression/Expression.hpp"
#include "xo/object/ObjectConverter.hpp"
#include "xo/alloc/Object.hpp"
namespace xo {
namespace scm {
/** @brief state that may be shared across VirtualSchematikaMachine instances **/
struct VirtualSchematikaMachineFlyweight {
explicit VirtualSchematikaMachineFlyweight(gc::IAlloc * mm,
gp<GlobalEnv> env,
log_level log_level);
/** memory allocator for interpreter operation. **/
gc::IAlloc * object_mm_ = nullptr;
/** global environment **/
gp<GlobalEnv> toplevel_env_;
/** convert TaggedPtr->Object **/
xo::obj::ObjectConverter object_converter_;
/** control logging level. higher values -> more logging **/
log_level log_level_;
};
/** @class VirtualSchematikaMachine
* @brief Virtual machine implementing a Schematika interpreter
*
**/
class VirtualSchematikaMachine {
public:
using IAlloc = xo::gc::IAlloc;
public:
VirtualSchematikaMachine(IAlloc * mm, gp<GlobalEnv> toplevel_env, log_level log_level);
~VirtualSchematikaMachine();
gp<GlobalEnv> toplevel_env() const { return flyweight_.toplevel_env_; }
/** evaluate expression @p expr.
* borrows calling thread until completion
* return [value, error]. error ignored unless value is nullptr.
* conversely when value is nullptr, error gives details of original
* error.
*
* Evaluate schematika expression @p expr in environment @p env
**/
std::pair<gp<Object>, SchematikaError> eval(bp<Expression> expr, gp<GlobalEnv> env);
/** evaluate expression @p expr in toplevel environment **/
std::pair<gp<Object>, SchematikaError> toplevel_eval(bp<Expression> expr);
private:
/** Not moveable or copyable.
* One constraint is member variables added to flyweight_.object_mm_
* as GC roots, with no provision for unwinding later.
**/
VirtualSchematikaMachine(const VirtualSchematikaMachine &) = delete;
VirtualSchematikaMachine(VirtualSchematikaMachine &&) = delete;
/** borrow calling thread to run schematika machine
* indefinitely, or until null continuation
**/
void run();
/** execute vsm instruction in program counter.
* Note: may possibly be able to replace with just opcode
*
* Registers:
* - expr_ input, caller saves
* - env_ input, caller saves
* - cont_ input, caller saves
* - stack_ input, caller saves
* - value_ output
* - error_ output
*
**/
void execute_one();
/* design note:
* - eval_xxx_op() methods are primary VSM transitions,
* in the sense that they begin a sequence of transitions to interpret a
* particular kind of expression
* - do_xxx_op() methods represent secondary VSM transitions,
* that continue an instruction sequence that was initiated by a preceding
* eval_xxx_op() method
*/
/** interpret literal constant expression **/
void eval_constant_op();
/** interpreter literal primitive expression
* (these appear implicitly as result of builtin operators like {+, ==, ..})
**/
void eval_primitive_op();
/** execute define expression (finished in do_complete_assign_op()) **/
void eval_define_op();
/** execute assign expression (finishes in do_complete_assign_op()) **/
void eval_assign_op();
/** continue after establishing value fo rhs of define exprsssion **/
void do_complete_assign_op();
/** interpret variable expression **/
void eval_variable_op();
/** interpret if-expression **/
void eval_ifexpr_op();
/** continue after establish value of test expression **/
void do_complete_ifexpr_op();
/** interpret sequence **/
void eval_sequence_op();
/** continue after establishing value for a sequence element **/
void do_complete_sequence_op();
/** interpret apply-expression (i.e. function call) **/
void eval_apply_op();
/** continue assembling args for a function call;
* transition to (interpretation of) called function once all arguments
* are evaluated.
**/
void do_complete_evalargs_op();
/** execute function application, given actuals in top stack frame **/
void apply_op();
/** goto error state with message @p err **/
void report_error(const std::string & err);
/** implementation class; contains instruction implementations **/
friend struct VsmOps;
private:
/** program counter.
* (Perhaps replace with VsmInstr::Opcode ?)
**/
const VsmInstr * pc_ = nullptr;
/** register to hold Schematika expression to drive @ref execute_one.
*
* caller saves!
**/
rp<Expression> expr_;
/** holds bindings for all schematika variables, to drive @ref execute_one.
* execute_one will not save this
*
* caller saves!
**/
gp<GlobalEnv> env_;
/** vsm stack. callee saves!
**/
gp<VsmStackFrame> stack_;
/** non-error result value from eval() / apply()
*
* output register: caller must save
**/
gp<Object> value_;
/** error result value from eval() / apply()
*
* output regisetr: caller must save
**/
SchematikaError error_;
/** continuation
* (Perhaps replace with VsmInstr::Opcode ?)
*
* input register: callee saves!
**/
const VsmInstr * cont_ = nullptr;
/** possibly-shared data **/
VirtualSchematikaMachineFlyweight flyweight_;
};
} /*namespace scm*/
} /*namespace xo*/
/* end VirtualSchematikaMachine.hpp */

View file

@ -1,80 +0,0 @@
/** @file VsmInstr.hpp **/
#pragma once
#include <string_view>
namespace xo {
namespace scm {
class VirtualSchematikaMachine; // see VirtualSchematikaMachine.hpp
/** @class VmInstr
* @brief Represent a particular vritual schematika machine instruction
*
* A vsm instruction acts on a virtual schematika machine instance.
**/
class VsmInstr
{
public:
enum class Opcode {
/** Halt virtual schematika machine **/
halt,
/** Evaluate a schematika expression.
* See VirtualSchematikaMachine::eval()
**/
eval,
/** assign to variable + continue
*
* stack: frame with:
* [0] lhs : variable to assign
**/
complete_assign,
/** execute ifexpr branch, given
* result of test expression has been established
**/
complete_ifexpr,
/** execute remainder of expression sequence
**/
complete_sequence,
/** execute remainder of argument sequence evaluation;
* subsidiary to marshalling a function call
**/
complete_evalargs,
/** Call a function. Arguments have been evaluated
* and are in top stack frame, in order,
* starting with target function itself
**/
apply,
/** choose branch of if-expression + continue
*
* stack: frame with
* [0] ifexpr : original if-expression
**/
N_Opcode
};
//using ActionFn = void (*)(VirtualSchematikaMachine * vm);
public:
VsmInstr(Opcode opcode, std::string_view name);
Opcode opcode() const { return opcode_; }
private:
/** unique opcode for this instruction **/
Opcode opcode_;
/** **/
std::string_view name_;
//ActionFn action_;
};
}
}
/* end VsmInstr.hpp */

View file

@ -1,83 +0,0 @@
/** @file VsmStackFrame.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include "VsmInstr.hpp"
#include "xo/object/CVector.hpp"
#include "xo/alloc/Object.hpp"
namespace xo {
namespace scm {
/** @class VsmStackFrame
* @brief Virtual Schematika Machine stack frame
*
* Intending to use the "cheney on the MTA" strategy,
* i.e. allocate frames using GC's bump allocator.
*
* Parallels LocalEnv, but VSM implementation isn't reflected
**/
class VsmStackFrame : public Object {
public:
VsmStackFrame(gc::IAlloc * mm, gp<VsmStackFrame> p, std::size_t n, const VsmInstr * cont);
/** create frame using allocator @p mm,
* with parent @p p and exactly @p n_slot object pointers.
**/
static gp<VsmStackFrame> make(gc::IAlloc * mm,
gp<VsmStackFrame> p,
std::size_t n_slot,
const VsmInstr * cont);
/** create new stack frame using allocator @p mm,
* with parent frame @p p; new frame contains values @p s0
**/
static gp<VsmStackFrame> push1(gc::IAlloc * mm,
gp<VsmStackFrame> p,
gp<Object> s0,
const VsmInstr * cont);
/** create new stack frame using allocator @p mm,
* with parent frame @p p; new frame contains values @p s0, @p s1
**/
static gp<VsmStackFrame> push2(gc::IAlloc * mm,
gp<VsmStackFrame> p,
gp<Object> s0,
gp<Object> s1,
const VsmInstr * cont);
/** reflect VsmStackFrame object representation **/
static void reflect_self();
gp<VsmStackFrame> parent() const { return parent_; }
std::size_t size() const { return slot_v_.size(); }
const obj::CVector<gp<Object>> & argv() const { return slot_v_; }
const VsmInstr * continuation() const { return cont_; }
gp<Object> operator[](std::size_t i) const { return slot_v_[i]; }
gp<Object> & operator[](std::size_t i) { return slot_v_[i]; }
// inherited from Object..
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;
virtual std::size_t _shallow_size() const final override;
virtual Object * _shallow_copy(gc::IAlloc *) const final override;
virtual std::size_t _forward_children(gc::IAlloc *) final override;
private:
/** parent stack frame **/
gp<VsmStackFrame> parent_;
/** stored state **/
obj::CVector<gp<Object>> slot_v_;
/** proceed to this continuation when popping this frame **/
const VsmInstr * cont_ = nullptr;
};
} /*namespace scm*/
} /*namespace xo*/
/* end VsmStackFrame.hpp */

View file

@ -1,21 +0,0 @@
/** @file init_interpreter.hpp
*
* author: Roland Conybeare, Nov 2025
**/
#pragma once
#include "xo/subsys/Subsystem.hpp"
namespace xo {
/* tag to represent the interpreter/ subsystem in ordered initialization */
enum S_interpreter_tag {};
template<>
struct InitSubsys<S_interpreter_tag> {
static void init();
static InitEvidence require();
};
}
/* end init_interpreter.hpp */

View file

@ -1,94 +0,0 @@
/** @file BuiltinPrimitives.cpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "BuiltinPrimitives.hpp"
#include "Integer.hpp"
#include "Primitive.hpp"
#include "xo/expression/PrimitiveExpr.hpp"
#include "xo/object/ObjectConversion.hpp"
#include "xo/reflect/Reflect.hpp"
#include <cstdint>
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::TaggedPtr;
using xo::reflect::TypeDescr;
namespace scm {
int64_t
add64(int64_t x, int64_t y)
{
return x + y;
}
void
BuiltinPrimitives::install(gc::IAlloc * mm, gp<GlobalEnv> env)
{
scope log(XO_DEBUG(true));
// add(x,y)
{
gp<Object> rhs = xo::obj::make_primitive(mm, "add", add64);
TypeDescr td = Reflect::require_function<decltype(&add64)>();
rp<Variable> lhs = Variable::make("add", td);
gp<Object> * addr = env->establish_var(lhs.borrow());
*addr = rhs;
}
// i64 comparisons
// @cmp_eq2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_eq2_i64(), env);
// @cmp_ne2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_ne2_i64(), env);
// @cmp_lt2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_lt2_i64(), env);
// @cmp_le2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_le2_i64(), env);
// @cmp_gt2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_gt2_i64(), env);
// @cmp_ge2_i64
install_pm(mm, PrimitiveExpr_cmp_i64::make_cmp_ge2_i64(), env);
// i64 arithmetic
// @add2_i64
install_pm(mm, PrimitiveExpr_i64::make_add2_i64(), env);
// @sub2_i64
install_pm(mm, PrimitiveExpr_i64::make_sub2_i64(), env);
// @mul2_i64
install_pm(mm, PrimitiveExpr_i64::make_mul2_i64(), env);
// @div2_i64
install_pm(mm, PrimitiveExpr_i64::make_div2_i64(), env);
// ----------------------------------------------------------------
// @add2_f64
install_pm(mm, PrimitiveExpr_f64::make_add2_f64(), env);
// @sub2_f64
install_pm(mm, PrimitiveExpr_f64::make_sub2_f64(), env);
// @mul2_f64
install_pm(mm, PrimitiveExpr_f64::make_mul2_f64(), env);
// @div2_f64
install_pm(mm, PrimitiveExpr_f64::make_div2_f64(), env);
}
}
}
/* end BuiltinPrimitives.cpp */

View file

@ -1,24 +0,0 @@
# interpreter/CMakeLists.txt
set(SELF_LIB xo_interpreter)
set(SELF_SRCS
init_interpreter.cpp
Schematika.cpp
BuiltinPrimitives.cpp
LocalEnv.cpp
GlobalEnv.cpp
VirtualSchematikaMachine.cpp
VsmInstr.cpp
VsmStackFrame.cpp
ExpressionBoxed.cpp
)
find_package(Threads REQUIRED)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
xo_dependency(${SELF_LIB} xo_object)
xo_dependency(${SELF_LIB} xo_expression)
xo_dependency(${SELF_LIB} xo_reader)
xo_external_target_dependency(${SELF_LIB} replxx replxx::replxx)
target_link_libraries(${SELF_LIB} PUBLIC Threads::Threads)
xo_headeronly_dependency(${SELF_LIB} subsys)

View file

@ -1,59 +0,0 @@
/** @file ExpressionBoxed.cpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "ExpressionBoxed.hpp"
#include "xo/reflect/Reflect.hpp"
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::TaggedPtr;
namespace scm {
ExpressionBoxed::ExpressionBoxed(bp<Expression> c) : contents_{c.promote()}
{}
gp<ExpressionBoxed>
ExpressionBoxed::make(gc::IAlloc * mm,
bp<Expression> c)
{
return new (MMPtr(mm)) ExpressionBoxed(c);
}
TaggedPtr
ExpressionBoxed::self_tp() const
{
return Reflect::make_tp(const_cast<ExpressionBoxed *>(this));
}
void
ExpressionBoxed::display(std::ostream & os) const
{
os << contents_;
}
std::size_t
ExpressionBoxed::_shallow_size() const
{
return sizeof(ExpressionBoxed);
}
Object *
ExpressionBoxed::_shallow_copy(gc::IAlloc * mm) const
{
Cpof cpof(mm, this);
return new (cpof) ExpressionBoxed(*this);
}
std::size_t
ExpressionBoxed::_forward_children(gc::IAlloc *)
{
return _shallow_size();
}
} /*namespace scm*/
} /*namespace xo*/
/* end ExpressionBoxed.cpp */

View file

@ -1,127 +0,0 @@
/** @file GlobalEnv.cpp **/
#include "GlobalEnv.hpp"
#include "xo/reflect/Reflect.hpp"
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::TaggedPtr;
namespace scm {
gp<GlobalEnv>
GlobalEnv::make_empty(gc::IAlloc * mm, const rp<GlobalSymtab> & symtab)
{
/* by design: GlobalEnv and GlobalEnv.slot_map_ are heap-allocated */
return new GlobalEnv(mm, symtab);
}
GlobalEnv::GlobalEnv(const GlobalEnv & x)
: mm_{x.mm_},
symtab_{x.symtab_},
slot_map_{std::make_unique<map_type>(*x.slot_map_)}
{
}
GlobalEnv::GlobalEnv(gc::IAlloc * mm,
const rp<GlobalSymtab> & symtab) : mm_{mm},
symtab_{symtab},
slot_map_{std::make_unique<map_type>()}
{}
bool
GlobalEnv::local_contains_var(const std::string & vname) const
{
return symtab_->lookup_local(vname).get();
}
gp<Object> *
GlobalEnv::lookup_slot(const std::string & vname)
{
scope log(XO_DEBUG(true), xtag("name", vname));
assert(slot_map_.get());
auto ix = slot_map_->find(vname);
if (ix == slot_map_->end()) {
return nullptr;
} else {
log && log("binding found", xtag("vname", vname));
return &(ix->second);
}
}
gp<Object> *
GlobalEnv::establish_var(bp<Variable> var)
{
scope log(XO_DEBUG(true), xtag("name", var->name()), xtag("type", var->valuetype()));
// Warning: altering declared type for an already-existing variable
// invalidates any type checking that relied on that variable.
//
// Ignoring this problem for now.
//
// Actual solution might look like:
// - keep track of which functions/defs depend on each global variable.
// - invalidate any jit / types for such variables.
// - maybe use seqno's to track
// - re-check / re-complie
// - need to admit invalid states.
// suppose have mutually recursive functions f(), g()
// want ability to modify type signatures separately
//
// Alternatives:
// - forbid changing type of an already-established variable
// Actually: can't even change values if we intend supporting dependent types
// - quietly number variables so new definitions shadow old ones but don't
// affect previously-encountered expressions
this->symtab_->require_global(var->name(), var);
gp<Object> &slot = (*this->slot_map_)[var->name()];
/* discard any pre-existing value, we're redefining a variable */
slot = gp<Object>();
return &slot;
}
TaggedPtr
GlobalEnv::self_tp() const
{
return Reflect::make_tp(const_cast<GlobalEnv *>(this));
}
void
GlobalEnv::display(std::ostream & os) const
{
os << "<global-env" << xtag("n", slot_map_->size()) << ">";
}
std::size_t
GlobalEnv::_shallow_size() const
{
return sizeof(GlobalEnv);
}
Object *
GlobalEnv::_shallow_copy(gc::IAlloc * mm) const
{
Cpof cpof(mm, this);
return new (cpof) GlobalEnv(*this);
}
std::size_t
GlobalEnv::_forward_children(gc::IAlloc * gc)
{
for (auto & ix : *slot_map_) {
Object::_forward_inplace(ix.second, gc);
}
return _shallow_size();
}
} /*namespace scm*/
} /*namespace xo*/
/* end GlobalEnv.cpp */

View file

@ -1,185 +0,0 @@
/** @file LocalEnv.cpp **/
#include "LocalEnv.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/reflect/StructReflector.hpp"
#include <cstring>
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::StructReflector;
using xo::reflect::TypeDescrW;
using xo::reflect::TaggedPtr;
using xo::reflect::TypeDescrExtra;
using xo::reflect::EstablishTypeDescr;
using xo::reflect::StlVectorTdx;
using xo::print::quot;
namespace scm {
namespace {
std::size_t
slot_array_size(std::size_t n) {
return n * sizeof(gp<Object>);
}
}
gp<LocalEnv>
LocalEnv::make(gc::IAlloc * mm,
gp<LocalEnv> p,
const rp<LocalSymtab> & s,
std::size_t n)
{
if (s) {
assert(static_cast<int>(n) == s->n_arg());
}
return new (MMPtr(mm)) LocalEnv(mm, p, s, n);
}
LocalEnv::LocalEnv(gc::IAlloc * mm,
gp<LocalEnv> p,
const rp<LocalSymtab> & s,
std::size_t n) : parent_{p},
symtab_{s},
slot_v_{mm, n}
{}
bool
LocalEnv::local_contains_var(const std::string & vname) const
{
assert(symtab_.get());
return symtab_->lookup_local(vname);
}
gp<Object> *
LocalEnv::lookup_slot(const std::string & vname)
{
binding_path b = symtab_->lookup_local_binding(vname);
if (b.i_link_ == 0) {
assert((b.j_slot_ >= 0) && (static_cast<size_t>(b.j_slot_) < slot_v_.size()));
return &(slot_v_[b.j_slot_]);
}
if (parent_.get()) {
return parent_->lookup_slot(vname);
}
return nullptr;
}
gp<Object> *
LocalEnv::establish_var(bp<Variable> v)
{
assert(v);
throw std::runtime_error(tostr("LocalEnv::establish_var:"
" inserting new variables not supported for LocalEnv",
xtag("v.name", v->name())));
}
TaggedPtr
LocalEnv::self_tp() const
{
return Reflect::make_tp(const_cast<LocalEnv *>(this));
}
void
LocalEnv::display(std::ostream & os) const
{
os << "<local-env"
<< xtag("n", slot_v_.size());
#ifdef NOT_YET
for (std::size_t i = 0, n = n_slot(); i < n; ++i) {
char buf[24];
snprintf(buf, sizeof(buf), "v[%lu]", i);
os << xtag(buf, lookup(i));
}
#endif
os << ">";
}
std::size_t
LocalEnv::_shallow_size() const
{
std::size_t retval = sizeof(LocalEnv);
retval += gc::IAlloc::with_padding(slot_array_size(slot_v_.size()));
return retval;
}
Object *
LocalEnv::_shallow_copy(gc::IAlloc * mm) const
{
Cpof cpof(mm, this);
size_t z = this->size();
LocalEnv * copy = new (cpof) LocalEnv(cpof.mm_, parent_, symtab_, z);
void * v_dest = copy->slot_v_.v_;
if (slot_v_.v_) {
::memcpy(v_dest, slot_v_.v_, slot_array_size(z));
}
#ifdef OBSOLETE
for (size_t i = 0, n = n_slot_; i < n; ++i) {
copy->v_[i] = v_[i];
}
#endif
return copy;
}
std::size_t
LocalEnv::_forward_children(gc::IAlloc * gc)
{
static_assert(decltype(symtab_)::is_gc_ptr == false);
Object::_forward_inplace(parent_, gc);
// Object::_forward_inplace(symtab_); // not a gp yet
for (std::size_t i = 0, n = slot_v_.size(); i < n; ++i) {
Object::_forward_inplace((*this)[i], gc);
}
return _shallow_size();
}
void
LocalEnv::reflect_self()
{
StructReflector<LocalEnv> sr;
if (sr.is_incomplete()) {
/* reflect CVector<gp<Object>>
*
* note: placement here works b/c CVector<T> not used anywhere else
*/
using VectorType = obj::CVector<gp<Object>>;
/* custom reflection for array of Object pointers.
* Can use StlVectorTdx here, treating CVector<T> as a vector
* via .size() and .operator[] members
*/
std::unique_ptr<TypeDescrExtra> tdx1
= std::make_unique<StlVectorTdx<VectorType>>();
TypeDescrW td1
= EstablishTypeDescr::establish<VectorType>();
td1->assign_tdextra(Reflect::get_final_invoker<VectorType>(),
std::move(tdx1));
REFLECT_MEMBER(sr, parent);
REFLECT_MEMBER(sr, slot_v);
}
}
} /*namespace scm*/
} /*namespace xo*/
/* end LocalEnv.cpp */

View file

@ -1,284 +0,0 @@
/** @file Schematika.cpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "Schematika.hpp"
#include "VirtualSchematikaMachine.hpp"
#include "BuiltinPrimitives.hpp"
#include "GlobalEnv.hpp"
#include "xo/reader/reader.hpp"
#include <replxx.hxx>
#include <ostream>
#include <unistd.h> // for STDIN_FILENO on OSX
namespace xo {
using xo::gc::IAlloc;
using xo::gc::GC;
using xo::print::ppconfig;
using xo::print::ppstate_standalone;
using replxx::Replxx;
using namespace std;
namespace scm {
class Schematika::Impl {
public:
/** note: choosing to have Schemtika::Impl
* rather than VirtualSchematikaMachine to own allocator
* to preserve option to share it
**/
Impl(const Config & config, up<IAlloc> mm, gp<GlobalEnv> toplevel_env);
~Impl();
/** create instance + allocator **/
static up<Impl> make(const Config & cfg);
/** borrow calling thread to run interactive read-eval-print loop;
* input from stdin, output to stdout.
**/
void interactive_repl();
void welcome(std::ostream & os);
/** get one line of input. prompt if @p interactive,
* with prompt depending on @p parser_stack_size.
* Use @p rx to perform line editing (when @p interactive).
* Store completed line in @p input.
**/
bool replxx_getline(bool interactive,
std::size_t parser_stack_size,
replxx::Replxx & rx,
std::string & input);
private:
/** configuration **/
Config config_;
/** ownership for memory allocator / garbage collector;
* @ref vsm_ holds naked pointer, so this could in principle be nullptr
* in case want to maintain allocator ownership from outside.
*
* note: must appear before @ref vsm_, so latter gets destroyed first
**/
up<IAlloc> mm_;
/** schematika interpreter **/
VirtualSchematikaMachine vsm_;
};
Schematika::Impl::Impl(const Config & config, up<IAlloc> mm, gp<GlobalEnv> toplevel_env) :
config_{config},
mm_{std::move(mm)},
vsm_{mm_.get(), toplevel_env, config.vsm_log_level_}
{
}
Schematika::Impl::~Impl() = default;
up<Schematika::Impl>
Schematika::Impl::make(const Config & cfg)
{
up<IAlloc> mm = GC::make(cfg.gc_config_);
rp<GlobalSymtab> symtab = GlobalSymtab::make_empty();
gp<GlobalEnv> env = GlobalEnv::make_empty(mm.get(), symtab);
/* also see VirtualSchematikaMachineFlyweight::Impl::Impl,
* for BuiltinPrimitives::install_interpreter_conversions()
*/
BuiltinPrimitives::install(mm.get(), env);
return std::make_unique<Impl>(cfg, std::move(mm), env);
}
void
Schematika::Impl::welcome(std::ostream & os)
{
using namespace std;
os << "read-eval-print loop for schematika expressions" << endl;
os << " ctrl-a/ctrl-e beginning/end of line" << endl;
os << " ctrl-u delete entire line" << endl;
os << " ctrl-k delete to end of line" << endl;
os << " meta-<bs> backward delete word" << endl;
os << " <up>|meta-p previous command from history" << endl;
os << " <down>|meta-n next command from history" << endl;
os << " <pgup>/<pgdown> page through history faster" << endl;
os << " ctrl-s/ctrl-r forward/backward history search" << endl;
os << endl;
}
// similar helper in exprreplxx.cpp
//
bool
Schematika::Impl::replxx_getline(bool interactive,
std::size_t parser_stack_size,
replxx::Replxx & rx,
std::string & input)
{
using namespace std;
char const * prompt = "";
if (interactive) {
if (parser_stack_size <= 1)
prompt = "> ";
else
prompt = ". ";
}
/* input_cstr: next line of input from replxx library */
const char * input_cstr = rx.input(prompt);
bool retval = (input_cstr != nullptr);
if (retval) {
/* got new input */
input = input_cstr;
}
rx.history_add(input);
input.push_back('\n');
return retval;
}
void
Schematika::Impl::interactive_repl()
{
scope log(XO_DEBUG(true));
using span_type = xo::scm::span<const char>;
bool interactive = isatty(STDIN_FILENO);
Replxx rx;
rx.set_max_history_size(config_.history_size);
rx.history_load(config_.history_file);
// rx.bind_key_internal(Replxx::KEY::control('p'), "history_previous");
// rx.bind_key_internal(Replxx::KEY::control('n'), "history_next");
reader rdr(vsm_.toplevel_env()->symtab(), config_.debug_flag);
rdr.begin_interactive_session();
string input_str;
bool eof = false;
span_type input;
std::size_t parser_stack_size = 0;
if (config_.welcome_flag_)
welcome(cerr);
while (replxx_getline(interactive, parser_stack_size, rx, input_str)) {
input = span_type::from_string(input_str);
while (!input.empty()) {
/**
* Three cases here:
* 1. available input is invalid (does not conform to schematika syntax).
* 1a. expr=nullptr
* 1b. consumed reads all available input
* 1c. psz=0
* 1d. error.is_error(); details including exact location where parsing failed.
* 1e. parser reset to top level.
* 2. available input represents prefix of a possibly-valid expression
* 2a. expr=nullptr;
* 2b. consumed reads all available input
* 2c. psz reflects nesting level after reading available input.
* 2d. error.is_not_an_error()
* 3. available input completes at least one expression
* 3a. expr contains first completed top-level expression
* 3b. consumed reports portion of input up to end of expr
* 3c. psz=0
* 3d. error.is_not_an_error()
*
* expr :: rp<Expression> if non-null: the next expression from input
* consumed :: span<char> extent of input read up to next Expression
* psz :: size_t parser stack size
* error :: reader_error error details on parsing failure
**/
auto [expr, consumed, psz, error] = rdr.read_expr(input, eof);
if (expr) {
/** configuration for pretty-printing **/
ppconfig ppc;
ppstate_standalone pps(&cout, 0, &ppc);
//pps.prettyn(expr);
// TODO:
auto [ value, scm_error ] = this->vsm_.toplevel_eval(expr);
if (scm_error.is_error()) {
/* print error */
cout << "scm error: " << scm_error.what() << endl;
cout << "top-level expression: " << expr << endl;
} else {
/* print value */
cout << "scm result:" << endl;
cout << value << endl;
//pps.pretty(value);
}
} else if (error.is_error()) {
cout << "parsing error (detected in " << error.src_function() << "): " << endl << endl;
error.report(cout);
/* discard stashed remainder of input line
* (for nicely-formatted errors)
*/
rdr.reset_to_idle_toplevel();
break;
}
input = input.after_prefix(consumed);
parser_stack_size = psz;
}
/* here: input.empty() or error encountered */
cerr << endl;
}
auto [expr, _1, _2, error] = rdr.read_expr(input, true /*eof*/);
if (expr) {
ppconfig ppc;
ppstate_standalone pps(&cout, 0, &ppc);
pps.prettyn<rp<Expression>>(rp<Expression>(expr));
} else if (error.is_error()) {
cout << "parsing error (detected in " << error.src_function() << "): " << endl;
error.report(cout);
}
rx.history_save("repl_history.txt");
}
// ----- Schematika -----
Schematika::Schematika(const Config & cfg) : p_impl_{Impl::make(cfg)}
{}
Schematika::~Schematika()
{}
Schematika
Schematika::make(const Config & cfg)
{
return Schematika(cfg);
}
void
Schematika::interactive_repl()
{
p_impl_->interactive_repl();
}
}
}
/* end Schematika.cpp */

View file

@ -1,865 +0,0 @@
/** @file VirtualSchematikaMachine.cpp **/
#include "VirtualSchematikaMachine.hpp"
#include "VsmInstr.hpp"
#include "BuiltinPrimitives.hpp"
#include "ExpressionBoxed.hpp"
#include "xo/expression/Constant.hpp"
#include "xo/expression/PrimitiveExprInterface.hpp"
#include "xo/expression/DefineExpr.hpp"
#include "xo/expression/AssignExpr.hpp"
#include "xo/expression/Variable.hpp"
#include "xo/expression/IfExpr.hpp"
#include "xo/expression/Sequence.hpp"
#include "xo/expression/Apply.hpp"
#include "xo/object/Procedure.hpp"
#include "xo/object/Primitive.hpp"
#include "xo/object/Integer.hpp"
#include "xo/object/Boolean.hpp"
#include "xo/alloc/GC.hpp"
/** continue after completing a VSM instruction;
* achieve by jumping to continuation.
**/
#define VSM_CONTINUE() this->pc_ = this->cont_; return;
/** report error and terminate VSM execution
**/
#define VSM_ERROR(msg) report_error(msg); return;
namespace xo {
using xo::gc::GC;
using xo::obj::Procedure;
using xo::obj::Integer;
using xo::obj::Boolean;
namespace scm {
struct VsmOps {
/** halt virtual scheme machine.
* This will cause innermost run() to return to its caller
**/
static VsmInstr halt_op;
/** evaluate an expression.
* - opcode is Opcode::eval
* - expression in register @ref expr_
**/
static VsmInstr eval_op;
/** assign variable after evaluating rhs of a define-expression or assign-expression
* - opcode is Opcode::complete_assign
* - top stack frame contains {lhs, cont}
**/
static VsmInstr complete_assign_op;
/** choose branch of if-expr after evaluating test condition.
* - opcode is Opcode::complete_ifexpr
* - top stack frame contains {ifexpr, cont}
**/
static VsmInstr complete_ifexpr_op;
/** proceed to next element of sequence-expr.
* - opcode is Opcode::complete_sequence
* - top stack frame contains {seq, next, cont}
*/
static VsmInstr complete_sequence_op;
/** proceed to next argument in apply-expr
* - opcode is Opcode::eval_collect_args
* - top stack frame contains {apply, targetarg, cont}
*/
static VsmInstr complete_evalargs_op;
/** call a procedure, where evaluated arguments (including target function)
* are in top stack frame.
* - opcode is Opcode::apply
* - top stack frame contains evaluated arguments.
**/
static VsmInstr apply_op;
};
VsmInstr
VsmOps::halt_op{VsmInstr::Opcode::halt, "halt"};
VsmInstr
VsmOps::eval_op{VsmInstr::Opcode::eval, "eval"};
VsmInstr
VsmOps::complete_assign_op{VsmInstr::Opcode::complete_assign, "complete-assign"};
VsmInstr
VsmOps::complete_ifexpr_op{VsmInstr::Opcode::complete_ifexpr, "complete-ifexpr"};
VsmInstr
VsmOps::complete_sequence_op{VsmInstr::Opcode::complete_sequence, "complete-sequence"};
VsmInstr
VsmOps::complete_evalargs_op{VsmInstr::Opcode::complete_evalargs, "complete-evalargs"};
VsmInstr
VsmOps::apply_op{VsmInstr::Opcode::apply, "apply"};
// ----- VirtualSchematikaMachineFlyweight -----
VirtualSchematikaMachineFlyweight::VirtualSchematikaMachineFlyweight(gc::IAlloc * mm,
gp<GlobalEnv> env,
log_level ll) :
object_mm_{mm},
toplevel_env_{env},
log_level_{ll}
{
}
// ----- VirtualSchematikaMachine -----
VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm,
gp<GlobalEnv> env,
log_level ll) : flyweight_{mm, env, ll}
{
scope log(XO_DEBUG(true), xtag("env", env), xtag("symtab", env->symtab()));
this->env_ = env;
// gc roots
gc::GC * gc = GC::from(mm);
if (gc) {
assert((gc->gc_in_progress() == false) && "cannot add roots while GC running");
gc->add_gc_root_dwim(&env_);
gc->add_gc_root_dwim(&value_);
} else {
// Want to support VSM with arena-allocator-only;
// if only for unit testing.
}
// TODO: install builtin primitives here
}
VirtualSchematikaMachine::~VirtualSchematikaMachine()
{
gc::GC * gc = GC::from(flyweight_.object_mm_);
if (gc) {
assert((gc->gc_in_progress() == false) && "cannot remove roots while GC running");
gc->remove_gc_root_dwim(&env_);
gc->remove_gc_root_dwim(&value_);
} else {
// nothing to do in arena-only mode
}
}
std::pair<gp<Object>,
SchematikaError>
VirtualSchematikaMachine::toplevel_eval(bp<Expression> expr)
{
return this->eval(expr, this->env_);
}
std::pair<gp<Object>,
SchematikaError>
VirtualSchematikaMachine::eval(bp<Expression> expr, gp<GlobalEnv> env)
{
scope log(XO_DEBUG(true), xtag("env", env), xtag("symtab", env->symtab()));
this->pc_ = &VsmOps::eval_op;
this->expr_ = expr.promote();
this->env_ = env;
this->stack_ = nullptr;
this->value_ = nullptr;
this->error_ = SchematikaError();
this->cont_ = &VsmOps::halt_op;
this->run();
return std::make_pair(this->value_, this->error_);
}
void
VirtualSchematikaMachine::run()
{
while(pc_)
this->execute_one();
}
void
VirtualSchematikaMachine::execute_one()
{
scope log(XO_DEBUG(true));
log && log(xtag("pc", pc_), xtag("cont", cont_));
log && log(xtag("stack", stack_));
using Opcode = VsmInstr::Opcode;
switch (pc_->opcode()) {
case Opcode::halt:
{
this->pc_ = nullptr;
this->cont_ = nullptr;
break;
}
case Opcode::eval:
{
log && log("Opcode::eval");
/* generally speaking: opcode will be 1:1 with extypes */
switch (expr_->extype()) {
case exprtype::constant:
log && log("eval -> constant");
this->eval_constant_op();
break;
case exprtype::primitive:
log && log("eval -> primitive");
this->eval_primitive_op();
break;
case exprtype::define:
log && log("eval -> define");
this->eval_define_op();
break;
case exprtype::assign:
log && log("eval -> assign");
this->eval_assign_op();
break;
case exprtype::variable:
log && log("eval -> variable");
this->eval_variable_op();
break;
case exprtype::ifexpr:
log && log("eval -> ifexpr");
this->eval_ifexpr_op();
break;
case exprtype::sequence:
log && log("eval -> sequence");
this->eval_sequence_op();
break;
case exprtype::apply:
log && log("eval -> apply");
this->eval_apply_op();
break;
case exprtype::invalid:
case exprtype::lambda:
case exprtype::convert:
case exprtype::n_expr:
this->pc_ = nullptr;
this->value_ = nullptr;
this->error_ = SchematikaError(tostr("execute_vsm: not implemented",
xtag("extype", expr_->extype())));
this->cont_ = nullptr;
break;
}
}
break;
case Opcode::complete_assign:
this->do_complete_assign_op();
break;
case Opcode::complete_ifexpr:
this->do_complete_ifexpr_op();
break;
case Opcode::complete_sequence:
this->do_complete_sequence_op();
break;
case Opcode::complete_evalargs:
this->do_complete_evalargs_op();
break;
case Opcode::apply:
this->apply_op();
break;
case Opcode::N_Opcode:
assert(false);
break;
}
}
void
VirtualSchematikaMachine::report_error(const std::string & err)
{
/* error short-circuits vsm operation */
this->pc_ = nullptr;
this->value_ = nullptr;
this->error_ = SchematikaError(err);
this->cont_ = nullptr;
}
void
VirtualSchematikaMachine::eval_constant_op()
{
using xo::scm::ConstantInterface;
scope log(XO_DEBUG(true));
bp<ConstantInterface> expr = ConstantInterface::from(expr_);
assert(expr);
this->value_ = flyweight_.object_converter_.tp_to_object(flyweight_.object_mm_,
expr->value_tp(),
false);
if (this->value_.ptr()) {
log && log("got object: ", xtag("value", value_));
VSM_CONTINUE();
} else {
/* see ObjectConverter::ctor to add more builtin types
*
* generally conversion for a type Foo will appear in Foo.hpp
* see
* xo/object/Boolean.hpp
* xo/object/Integer.hpp
* xo/object/Float.hpp
* xo/object/String.hpp
*/
VSM_ERROR(tostr("constant_op: unable to convert native value to object",
xtag("id", expr->value_tp().td()->id()),
xtag("short_name", expr->value_tp().td()->short_name())));
}
}
void
VirtualSchematikaMachine::eval_primitive_op()
{
using xo::obj::Primitive;
using xo::reflect::TaggedPtr;
scope log(XO_DEBUG(true));
bp<PrimitiveExprInterface> expr = PrimitiveExprInterface::from(expr_);
const gp<Object> * slot = env_->lookup_slot(expr->name());
if (slot) {
this->value_ = *slot;
this->pc_ = cont_;
} else {
std::string err = tostr("no binding for primitive", xtag("name", expr->name()));
this->value_ = nullptr;
this->error_ = SchematikaError(err);
/* note: poor man's exception */
this->pc_ = nullptr;
this->cont_ = nullptr;
}
}
void
VirtualSchematikaMachine::eval_define_op()
{
using xo::scm::DefineExpr;
scope log(XO_DEBUG(true));
auto mm = flyweight_.object_mm_;
bp<DefineExpr> expr = DefineExpr::from(expr_);
assert(expr);
assert(env_.get());
// note: expecting nested define to have expanded iteself into
// applying a lambda
// note: establish lhs_var first, to allow for recursion, for example:
// def fact(n: i64) { if (n == 0) then 1; else n * fact(n-1); }
/** remembers promised variable type **/
this->env_->establish_var(expr->lhs_variable());
/** must promote rp<Expression> -> gp<ExpressionBoxed> **/
gp<ExpressionBoxed> lhs_0 = ExpressionBoxed::make(mm, expr->lhs_variable());
this->pc_ = &VsmOps::eval_op;
this->expr_ = expr->rhs();
/* when control arrives at .cont_, will have:
* .value_ -> result of evaluating expr->rhs()
*/
this->stack_ = VsmStackFrame::push1(mm, this->stack_, lhs_0, cont_);
/* .stack_:
* frame
* [0] = lhs_0 (boxed lhs Variable)
* ..
*/
this->cont_ = &VsmOps::complete_assign_op;
}
void
VirtualSchematikaMachine::eval_assign_op()
{
using xo::scm::AssignExpr;
scope log(XO_DEBUG(true));
auto mm = flyweight_.object_mm_;
bp<AssignExpr> assign = AssignExpr::from(expr_);
assert(assign.get());
assert(env_.get());
assert(assign->lhs().get());
assert(assign->rhs().get());
/* verify slot exists, before we evaluate rhs */
gp<Object> * slot = env_->lookup_slot(assign->lhs()->name());
if (slot) {
/** must promote rp<Expression> -> gp<ExpressionBoxed> **/
gp<ExpressionBoxed> lhs = ExpressionBoxed::make(mm, assign->lhs());
this->pc_ = &VsmOps::eval_op;
this->expr_ = assign->rhs();
/* when control arrives at .cont_, will have:
* .value_ -> result of evaluating assign->rhs()
*/
this->stack_ = VsmStackFrame::push1(mm, this->stack_, lhs, cont_);
/* .stack_:
* frame
* [0] = lhs (boxed lhs Variable)
* ..
*/
this->cont_ = &VsmOps::complete_assign_op;
} else {
std::string err = tostr("no binding for lhs of assignment", xtag("name", assign->lhs()->name()));
this->value_ = nullptr;
this->error_ = SchematikaError(err);
/* note: poor man's exception */
this->pc_ = nullptr;
this->cont_ = nullptr;
}
}
void
VirtualSchematikaMachine::do_complete_assign_op()
{
scope log(XO_DEBUG(true));
/*
* - value: contains result of evaluating rhs of define
* - stack: top frame has 1 slot, holds variable to receive assignment
*/
assert(value_.get());
assert(stack_.get());
assert(env_.get());
gp<VsmStackFrame> sp0 = this->stack_;
bp<Variable> var = Variable::from(ExpressionBoxed::from((*sp0)[0])->contents());
assert(var.get());
gp<Object> * slot = this->env_->establish_var(var);
assert(slot);
*slot = this->value_;
//this->value_ = this->value_; // preserve value from rhs of defexpr
this->stack_ = sp0->parent();
this->pc_ = this->cont_ = sp0->continuation();
}
void
VirtualSchematikaMachine::eval_variable_op()
{
using xo::scm::Variable;
scope log(XO_DEBUG(true));
bp<Variable> var = Variable::from(expr_);
assert(var.get());
assert(env_.get());
const gp<Object> * slot = env_->lookup_slot(var->name());
if (slot) {
this->value_ = *slot;
this->pc_ = cont_;
} else {
/* Unknown variable error will often be recognized in expression parser,
* in such cases this path won't be used.
*
* In interactive environment will need some kind of support for modifying
* code (e.g. replacing top-level functions/variables), and in particular,
* replacements may have different type signature.
* It's possible that allowing for such replacements winds up giving up
* typesafety guarantees. In that case this path may get activated after
* all.
*/
std::string err = tostr("no binding for variable", xtag("name", var->name()));
this->value_ = nullptr;
this->error_ = SchematikaError(err);
/* note: poor man's exception */
this->pc_ = nullptr;
this->cont_ = nullptr;
}
}
void
VirtualSchematikaMachine::eval_ifexpr_op()
{
using xo::scm::IfExpr;
scope log(XO_DEBUG(true));
gc::IAlloc * mm = flyweight_.object_mm_;
/** must promote bp<IfExpr> -> gp<ExpressionBoxed> **/
gp<ExpressionBoxed> ifexpr_boxed = ExpressionBoxed::make(mm, expr_);
bp<IfExpr> ifexpr = IfExpr::from(expr_);
assert(ifexpr.get());
assert(env_.get());
this->pc_ = &VsmOps::eval_op;
this->expr_ = ifexpr->test();
/* when control arrives at .cont_ will have:
* .value_ -> result of evaluating ifexpr->test()
*/
this->stack_ = VsmStackFrame::push1(mm, this->stack_, ifexpr_boxed, cont_);
/* .stack_:
* frame
* [0] = ifexpr (boxed expression)
*/
this->cont_ = &VsmOps::complete_ifexpr_op;
}
void
VirtualSchematikaMachine::do_complete_ifexpr_op()
{
using xo::scm::IfExpr;
scope log(XO_DEBUG(true));
/*
* - value: contains result of evaluating test condition of if-expr
* - stack: top frame has 1 slot, holds (boxed) if-expr itself
*/
assert(value_.get());
assert(stack_.get());
assert(env_.get());
gp<Boolean> test_value = gp<Boolean>::from(value_);
if (test_value.get()) {
gp<VsmStackFrame> sp0 = this->stack_;
bp<IfExpr> ifexpr = IfExpr::from(ExpressionBoxed::from((*sp0)[0])->contents());
assert(ifexpr.get());
this->pc_ = &VsmOps::eval_op;
if (test_value->value()) {
this->expr_ = ifexpr->when_true();
} else {
if (ifexpr->when_false()) {
this->expr_ = ifexpr->when_false();
} else {
/* 1-sided if-expr; evaluate to false */
this->expr_ = Constant<bool>::make(false);
}
}
this->stack_ = sp0->parent();
this->cont_ = sp0->continuation();
} else {
std::string err = tostr("expect boolean value for result of if-expr test", xtag("value", test_value));
this->value_ = nullptr;
this->error_ = SchematikaError(err);
/* note: poor man's exception */
this->pc_ = nullptr;
this->cont_ = nullptr;
}
}
void
VirtualSchematikaMachine::eval_sequence_op()
{
using xo::scm::Sequence;
scope log(XO_DEBUG(true));
gc::IAlloc * mm = flyweight_.object_mm_;
/** must promote bp<Sequence> -> gp<ExpressionBoxed> **/
gp<ExpressionBoxed> seq_boxed = ExpressionBoxed::make(mm, expr_);
bp<Sequence> seq = Sequence::from(expr_);
assert(seq.get());
assert(env_.get());
this->pc_ = &VsmOps::eval_op;
if (seq->size() == 0) {
/* for 0-size sequence, invent an expression */
this->expr_ = Constant<bool>::make(false);
} else {
this->expr_ = (*seq)[0];
}
if (seq->size() > 1) {
/* remainder */
gp<Integer> next = Integer::make(mm, 1);
/* when control arrives at .cont_ will have:
* .value_ -> result of evaluating last expr in seq
*/
this->stack_ = VsmStackFrame::push2(mm, stack_, seq_boxed, next, cont_);
/* .stack_:
* frame
* [0] = seq (boxed sequence)
* [1] = next (index of next seq member to evaluate)
* ..
*/
this->cont_ = &VsmOps::complete_sequence_op;
} else {
/* sequence completes when expr_ evaluated
* -> proceed with o.g. cont_
*/
}
}
void
VirtualSchematikaMachine::do_complete_sequence_op()
{
using xo::scm::Sequence;
scope log(XO_DEBUG(true));
/* - stack: top frame has 2 slots:
* [0] : seq (boxed Sequence)
* [1] : next (index of next seq element to eval
*/
assert(value_.get());
assert(stack_.get());
gp<VsmStackFrame> sp0 = this->stack_;
assert(sp0->size() == 2);
bp<Sequence> seq = Sequence::from(ExpressionBoxed::from((*sp0)[0])->contents());
gp<Integer> next_obj = Integer::from((*sp0)[1]);
size_t i_next = next_obj->value();
assert(i_next < seq->size());
this->pc_ = &VsmOps::eval_op;
this->expr_ = (*seq)[i_next];
if (i_next + 1 == seq->size()) {
/* last member of sequence -> tail call optimization */
this->stack_ = sp0->parent();
this->cont_ = sp0->continuation();
} else {
/* we can modify next_obj in place,
* since it's unique to frame sp0
*/
next_obj->assign_value(i_next + 1);
this->cont_ = &VsmOps::complete_sequence_op;
}
}
void
VirtualSchematikaMachine::eval_apply_op()
{
/* strategy:
* 1. calling sequence will involve two stack frames.
* 1a. the outer frame will hold 'final evaluated arguments'
* to the called function. When control transfers to that
* function, this frame will be at the top of stack_
* 1b. innert frame will be used by eval_apply_op() and
* helper do_eval_collect_args() to evaluate function
* arguments, and populate the outer frame.
*/
using xo::scm::Apply;
scope log(XO_DEBUG(true));
gc::IAlloc * mm = flyweight_.object_mm_;
/** must promote bp<Apply> -> gp<ExpressionBoxed> **/
gp<ExpressionBoxed> apply_boxed = ExpressionBoxed::make(mm, expr_);
bp<Apply> apply = Apply::from(expr_);
assert(apply.get());
size_t n = apply->n_arg() + 1;
/* reminder: argument 0 refers to the function being called */
gp<Integer> targetarg = Integer::make(mm, 0);
/* outer frame */
gp<VsmStackFrame> argstack = VsmStackFrame::make(mm, stack_, n, cont_);
/* scratch frame during call sequence.
* probably collect->cont_ will not be used?
*/
gp<VsmStackFrame> collect = VsmStackFrame::push2(mm,
argstack,
apply_boxed,
targetarg,
&VsmOps::complete_evalargs_op);
this->pc_ = &VsmOps::eval_op;
this->expr_ = apply->fn();
this->stack_ = collect;
this->cont_ = &VsmOps::complete_evalargs_op;
}
void
VirtualSchematikaMachine::do_complete_evalargs_op()
{
using xo::scm::Apply;
scope log(XO_DEBUG(true));
/* - stack: top frame has 2 slots
* [0] : apply (boxed Apply)
* [1] : targetarg index of next evaluated argument to deliver.
* (to corresponding slot in 2nd frame)
* - 2nd frame has n slots, where n = #of arguments at this site
* [0] : actual #0
* ..
* [targetarg-1] : actual #{targetarg-1}
*/
assert(value_.get());
assert(stack_.get());
gp<VsmStackFrame> sp0 = this->stack_;
assert(sp0.get());
assert(sp0->size() == 2);
bp<Apply> apply = Apply::from(ExpressionBoxed::from((*sp0)[0])->contents());
assert(apply.get());
gp<Integer> targetarg_obj = Integer::from((*stack_)[1]);
size_t targetarg = targetarg_obj->value();
/* note: apply->n_arg() doesn't count function itself */
assert(targetarg < apply->n_arg() + 1);
gp<VsmStackFrame> argstack = sp0->parent();
assert(argstack.get());
/* storing actual parameter in its correct position in call stackframe */
(*argstack)[targetarg] = value_;
++targetarg;
if (targetarg < apply->n_arg() + 1) {
/*
* arguments 0 .. #targetarg-1 already present in argstack
* arguments #targetarg .. #n still need to be evaluated
*/
/* ok to update in place, since Integer in sp0 is unique */
targetarg_obj->assign_value(targetarg);
rp<Expression> targetexpr = apply->lookup_arg(targetarg - 1);
this->pc_ = &VsmOps::eval_op;
this->expr_ = targetexpr;
assert(this->stack_.get() == sp0.get());
this->cont_ = &VsmOps::complete_evalargs_op;
} else {
/* all args evaluated: proceed to invoke evaluated function */
this->pc_ = &VsmOps::apply_op;
this->expr_ = nullptr;
this->stack_ = argstack;
/* unnecessary - will actually be set by apply_op() */
this->cont_ = argstack->continuation();
}
}
void
VirtualSchematikaMachine::apply_op()
{
scope log(XO_DEBUG(true));
auto mm = flyweight_.object_mm_;
// NOTE: Closures will have special handling.
// Could alternatively forward the whole problem
// (along with VSM state) to procedure implementation
/* stack: top frame has n slots for procedure with n canonical args */
gp<VsmStackFrame> sp0 = stack_;
assert(sp0->size() > 0);
gp<Procedure> fn = Procedure::from((*sp0)[0]);
if (fn->n_args() + 1 != sp0->size()) {
throw std::runtime_error(tostr("VirtualSchematikaMachine::apply_op:"
" argument mismatch in apply"
": k arguments supplied where n expected",
xtag("k", sp0->size() - 1),
xtag("n", fn->n_args())));
}
/* todo:
* check function signature?
* should have been guaranteed by expression parser,
* but complications in interactive session when variables redefined.
*/
gp<Object> retval = fn->apply_nocheck(mm, sp0->argv());
this->pc_ = this->cont_;
this->stack_ = sp0->parent();
this->value_ = retval;
}
} /*namespace scm*/
} /*namespace xo*/
/* end VirtualSchematikaMachine.cpp */

View file

@ -1,16 +0,0 @@
/** @file VsmInstr.cpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "VsmInstr.hpp"
namespace xo {
namespace scm {
VsmInstr::VsmInstr(Opcode opcode,
std::string_view name) : opcode_{opcode}, name_{name}
{}
}
}
/* end VsmInstr.cpp */

View file

@ -1,177 +0,0 @@
/** @file VsmStackFrame.cpp
*
* @author Roland Conybeare, Nov 2025
**/
#include "VsmStackFrame.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/reflect/StructReflector.hpp"
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::StructReflector;
using xo::reflect::TypeDescrW;
using xo::reflect::TaggedPtr;
using xo::reflect::TypeDescrExtra;
using xo::reflect::EstablishTypeDescr;
using xo::reflect::StlVectorTdx;
namespace scm {
namespace {
// TOOD: move into CVector
std::size_t
slot_array_size(std::size_t n) {
return n * sizeof(gp<Object>);
}
}
VsmStackFrame::VsmStackFrame(gc::IAlloc * mm,
gp<VsmStackFrame> p,
std::size_t n,
const VsmInstr * cont) : parent_{p},
slot_v_{mm, n},
cont_{cont}
{}
gp<VsmStackFrame>
VsmStackFrame::make(gc::IAlloc * mm,
gp<VsmStackFrame> p,
std::size_t n,
const VsmInstr * cont)
{
gp<VsmStackFrame> retval = new (MMPtr(mm)) VsmStackFrame(mm, p, n, cont);
for (std::size_t i = 0; i < n; ++i)
(*retval)[i] = nullptr;
return retval;
}
gp<VsmStackFrame>
VsmStackFrame::push1(gc::IAlloc * mm,
gp<VsmStackFrame> p,
gp<Object> s0,
const VsmInstr * cont)
{
gp<VsmStackFrame> retval = new (MMPtr(mm)) VsmStackFrame(mm, p, 1, cont);
(*retval)[0] = s0;
return retval;
}
gp<VsmStackFrame>
VsmStackFrame::push2(gc::IAlloc * mm,
gp<VsmStackFrame> p,
gp<Object> s0,
gp<Object> s1,
const VsmInstr * cont)
{
gp<VsmStackFrame> retval = new (MMPtr(mm)) VsmStackFrame(mm, p, 2, cont);
(*retval)[0] = s0;
(*retval)[1] = s1;
return retval;
}
TaggedPtr
VsmStackFrame::self_tp() const
{
return Reflect::make_tp(const_cast<VsmStackFrame *>(this));
}
void
VsmStackFrame::display(std::ostream & os) const
{
os << "<vsm-stack-frame"
<< xtag("n", slot_v_.size());
#ifdef NOT_YET
for (std::size_t i = 0, n = n_slot(); i < n; ++i) {
char buf[24];
snprintf(buf, sizeof(buf), "v[%lu]", i);
os << xtag(buf, lookup(i));
}
#endif
os << ">";
}
std::size_t
VsmStackFrame::_shallow_size() const
{
std::size_t retval = sizeof(VsmStackFrame);
retval += gc::IAlloc::with_padding(slot_array_size(slot_v_.size()));
return retval;
}
Object *
VsmStackFrame::_shallow_copy(gc::IAlloc * mm) const
{
Cpof cpof(mm, this);
size_t n = this->size();
VsmStackFrame * copy = new (cpof) VsmStackFrame(cpof.mm_, parent_, n, cont_);
void * v_dest = copy->slot_v_.v_;
if (slot_v_.v_) {
::memcpy(v_dest, slot_v_.v_, slot_array_size(n));
}
#ifdef OBSOLETE
for (size_t i = 0, n = n_slot_; i < n; ++i) {
copy->v_[i] = v_[i];
}
#endif
return copy;
}
std::size_t
VsmStackFrame::_forward_children(gc::IAlloc * gc)
{
Object::_forward_inplace(parent_, gc);
for (std::size_t i = 0, n = slot_v_.size(); i < n; ++i) {
Object::_forward_inplace((*this)[i], gc);
}
return _shallow_size();
}
void
VsmStackFrame::reflect_self()
{
StructReflector<VsmStackFrame> sr;
if (sr.is_incomplete() ) {
/* reflect CVector<gp<Object>>.
* duplicates similar code in LocalEnv::reflect_self()
*/
using VectorType = obj::CVector<gp<Object>>;
/* custom reflection for array of Object pointers.
* Can use StlVectorTdx here, treating CVector<T> as a vector
* via .size() and .operator[] members
*/
std::unique_ptr<TypeDescrExtra> tdx1
= std::make_unique<StlVectorTdx<VectorType>>();
TypeDescrW td1
= EstablishTypeDescr::establish<VectorType>();
td1->assign_tdextra(Reflect::get_final_invoker<VectorType>(),
std::move(tdx1));
REFLECT_MEMBER(sr, parent);
REFLECT_MEMBER(sr, slot_v);
}
}
} /*namespace scm*/
} /*namespace xo*/
/* end VsmStackFrame.cpp */

View file

@ -1,27 +0,0 @@
/** @file init_interpreter.cpp
*
* author: Roland Conybeare, Nov 2025
*/
#include "init_interpreter.hpp"
#include "LocalEnv.hpp"
#include "xo/subsys/Subsystem.hpp"
namespace xo {
using xo::scm::LocalEnv;
void
InitSubsys<S_interpreter_tag>::init()
{
LocalEnv::reflect_self();
}
InitEvidence
InitSubsys<S_interpreter_tag>::require()
{
return Subsystem::provide<S_interpreter_tag>("interpreter", &init);
}
} /*namespace xo*/
/* end init_interpreter.cpp */

View file

@ -1,12 +0,0 @@
# build unittest interpreter/utest
set(UTEST_EXE utest.interpreter)
set(UTEST_SRCS
interpreter_utest_main.cpp
LocalEnv.test.cpp
)
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
xo_self_dependency(${UTEST_EXE} xo_interpreter)
xo_dependency(${UTEST_EXE} xo_object)
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)

View file

@ -1,134 +0,0 @@
/** @file LocalEnv.test.cpp **/
#include "xo/interpreter/init_interpreter.hpp"
#include "xo/interpreter/LocalEnv.hpp"
#include "xo/object/Integer.hpp"
#include "xo/alloc/GC.hpp"
#include <catch2/catch.hpp>
#include <vector>
#include <cstdint>
namespace xo {
using xo::scm::LocalEnv;
using xo::obj::Integer;
using xo::gc::GC;
using xo::gc::ArenaAlloc;
using xo::gc::generation;
using xo::gc::generation_result;
using xo::reflect::TaggedPtr;
namespace ut {
static InitEvidence s_init = (InitSubsys<S_interpreter_tag>::require());
namespace {
struct Testcase_LocalEnv {
Testcase_LocalEnv(const std::vector<std::int32_t> & contents) : contents_{contents} {}
/* build xo::obj::Integer for each contents_[i], store in F[i] for new LocalEnv F */
std::vector<std::int32_t> contents_;
};
std::vector<Testcase_LocalEnv>
s_testcase_v = {
Testcase_LocalEnv({}),
Testcase_LocalEnv({}),
Testcase_LocalEnv({111}),
Testcase_LocalEnv({111, 222}),
};
}
TEST_CASE("LocalEnv", "[LocalEnv][interpreter]")
{
Subsystem::initialize_all();
constexpr bool c_debug_flag = false;
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
scope log(XO_DEBUG(c_debug_flag), xtag("test", "LocalEnv2"), xtag("i_tc", i_tc));
const Testcase_LocalEnv & tc = s_testcase_v[i_tc];
up<ArenaAlloc> alloc = ArenaAlloc::make("utest", 16384, c_debug_flag);
REQUIRE(alloc.get());
Object::mm = alloc.get();
std::size_t n = tc.contents_.size();
gp<LocalEnv> frame = LocalEnv::make(alloc.get(), nullptr /*parent*/, nullptr /*symtab*/, n);
TaggedPtr tp = frame->self_tp();
REQUIRE(tp.is_struct());
}
}
TEST_CASE("LocalEnv2", "[LocalEnv][gc][interpreter]")
{
Subsystem::initialize_all();
constexpr bool c_debug_flag = false;
try {
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
scope log(XO_DEBUG(c_debug_flag), xtag("test", "LocalEnv2"), xtag("i_tc", i_tc));
const Testcase_LocalEnv & tc = s_testcase_v[i_tc];
up<GC> gc = GC::make(
{.initial_nursery_z_ = 16384,
.initial_tenured_z_ = 32768,
.incr_gc_threshold_ = 4096,
.full_gc_threshold_ = 4096,
.object_stats_flag_ = true,
.debug_flag_ = c_debug_flag,
});
REQUIRE(gc.get());
/* use gc for all Object allocs */
GC * mm = gc.get();
Object::mm = mm;
std::size_t n = tc.contents_.size();
gp<Integer> x = Integer::make(gc.get(), 42);
gc->add_gc_root(reinterpret_cast<IObject **>(&x));
REQUIRE(gc->tospace_generation_of(x.ptr()) == generation_result::nursery);
gp<LocalEnv> frame = LocalEnv::make(gc.get(), nullptr /*parent*/, nullptr /*symtab*/, n);
LocalEnv ** frame_pp = frame.ptr_address();
gc->add_gc_root(reinterpret_cast<IObject **>(frame_pp));
/* verifying allocated in N1 */
REQUIRE(gc->tospace_generation_of(frame.ptr()) == generation_result::nursery);
for (std::size_t i = 0; i < n; ++i)
(*frame)[i] = Integer::make(mm, tc.contents_.at(i));
std::size_t expected_alloc_z = frame->_shallow_size();
REQUIRE(expected_alloc_z >= sizeof(LocalEnv) + n * sizeof(gp<Object>));
gc->request_gc(generation::nursery); // <<<<<<<<< GC here <<<<<<<<<
REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1);
REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0);
/* verify Integer x preserved across gc */
REQUIRE(gc->tospace_generation_of(x.ptr()) == generation_result::nursery);
/* verify LocalEnv preserved across gc */
REQUIRE(gc->tospace_generation_of(frame.ptr()) == generation_result::nursery);
REQUIRE(frame->size() == n);
for (std::size_t i = 0; i < n; ++i) {
//REQUIRE(Integer::from(frame->lookup(i)).ptr());
//REQUIRE(Integer::from(frame->lookup(i))->value() == tc.contents_.at(i));
}
}
} catch (std::exception & ex) {
std::cerr << "exception: " << ex.what() << std::endl;
REQUIRE(false);
}
}
}
}
/* end LocalEnv.test.cpp */

View file

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