From 9eaae04fdf0608de87867fe54f367fd9a1382d54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 22:12:50 -0400 Subject: [PATCH] git subrepo clone git@github.com:Rconybea/xo-interpreter.git xo-interpreter subrepo: subdir: "xo-interpreter" merged: "047658a5" upstream: origin: "git@github.com:Rconybea/xo-interpreter.git" branch: "main" commit: "047658a5" git-subrepo: version: "0.4.9" origin: "???" commit: "???" --- xo-interpreter/.gitrepo | 12 + xo-interpreter/CMakeLists.txt | 41 + xo-interpreter/README.md | 1 + .../cmake/xo-bootstrap-macros.cmake | 41 + .../cmake/xo_interpreterConfig.cmake.in | 8 + xo-interpreter/docs/CMakeLists.txt | 9 + xo-interpreter/docs/README | 41 + xo-interpreter/docs/_static/README | 1 + xo-interpreter/docs/_static/img/favicon.ico | Bin 0 -> 309936 bytes xo-interpreter/docs/conf.py | 39 + xo-interpreter/docs/index.rst | 12 + xo-interpreter/docs/install.rst | 202 ++++ xo-interpreter/example/CMakeLists.txt | 1 + xo-interpreter/example/replxx/CMakeLists.txt | 17 + xo-interpreter/example/replxx/replxx.cpp | 19 + .../xo/interpreter/BuiltinPrimitives.hpp | 36 + xo-interpreter/include/xo/interpreter/Env.hpp | 47 + .../xo/interpreter/ExpressionBoxed.hpp | 48 + .../include/xo/interpreter/GlobalEnv.hpp | 73 ++ .../include/xo/interpreter/LocalEnv.hpp | 95 ++ .../include/xo/interpreter/Schematika.hpp | 70 ++ .../xo/interpreter/SchematikaError.hpp | 28 + .../interpreter/VirtualSchematikaMachine.hpp | 187 ++++ .../include/xo/interpreter/VsmInstr.hpp | 80 ++ .../include/xo/interpreter/VsmStackFrame.hpp | 83 ++ .../xo/interpreter/init_interpreter.hpp | 21 + .../src/interpreter/BuiltinPrimitives.cpp | 94 ++ xo-interpreter/src/interpreter/CMakeLists.txt | 24 + .../src/interpreter/ExpressionBoxed.cpp | 59 ++ xo-interpreter/src/interpreter/GlobalEnv.cpp | 127 +++ xo-interpreter/src/interpreter/LocalEnv.cpp | 185 ++++ xo-interpreter/src/interpreter/Schematika.cpp | 284 ++++++ .../interpreter/VirtualSchematikaMachine.cpp | 865 ++++++++++++++++++ xo-interpreter/src/interpreter/VsmInstr.cpp | 16 + .../src/interpreter/VsmStackFrame.cpp | 177 ++++ .../src/interpreter/init_interpreter.cpp | 27 + xo-interpreter/utest/CMakeLists.txt | 12 + xo-interpreter/utest/LocalEnv.test.cpp | 134 +++ .../utest/interpreter_utest_main.cpp | 6 + 39 files changed, 3222 insertions(+) create mode 100644 xo-interpreter/.gitrepo create mode 100644 xo-interpreter/CMakeLists.txt create mode 100644 xo-interpreter/README.md create mode 100644 xo-interpreter/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-interpreter/cmake/xo_interpreterConfig.cmake.in create mode 100644 xo-interpreter/docs/CMakeLists.txt create mode 100644 xo-interpreter/docs/README create mode 100644 xo-interpreter/docs/_static/README create mode 100644 xo-interpreter/docs/_static/img/favicon.ico create mode 100644 xo-interpreter/docs/conf.py create mode 100644 xo-interpreter/docs/index.rst create mode 100644 xo-interpreter/docs/install.rst create mode 100644 xo-interpreter/example/CMakeLists.txt create mode 100644 xo-interpreter/example/replxx/CMakeLists.txt create mode 100644 xo-interpreter/example/replxx/replxx.cpp create mode 100644 xo-interpreter/include/xo/interpreter/BuiltinPrimitives.hpp create mode 100644 xo-interpreter/include/xo/interpreter/Env.hpp create mode 100644 xo-interpreter/include/xo/interpreter/ExpressionBoxed.hpp create mode 100644 xo-interpreter/include/xo/interpreter/GlobalEnv.hpp create mode 100644 xo-interpreter/include/xo/interpreter/LocalEnv.hpp create mode 100644 xo-interpreter/include/xo/interpreter/Schematika.hpp create mode 100644 xo-interpreter/include/xo/interpreter/SchematikaError.hpp create mode 100644 xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp create mode 100644 xo-interpreter/include/xo/interpreter/VsmInstr.hpp create mode 100644 xo-interpreter/include/xo/interpreter/VsmStackFrame.hpp create mode 100644 xo-interpreter/include/xo/interpreter/init_interpreter.hpp create mode 100644 xo-interpreter/src/interpreter/BuiltinPrimitives.cpp create mode 100644 xo-interpreter/src/interpreter/CMakeLists.txt create mode 100644 xo-interpreter/src/interpreter/ExpressionBoxed.cpp create mode 100644 xo-interpreter/src/interpreter/GlobalEnv.cpp create mode 100644 xo-interpreter/src/interpreter/LocalEnv.cpp create mode 100644 xo-interpreter/src/interpreter/Schematika.cpp create mode 100644 xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp create mode 100644 xo-interpreter/src/interpreter/VsmInstr.cpp create mode 100644 xo-interpreter/src/interpreter/VsmStackFrame.cpp create mode 100644 xo-interpreter/src/interpreter/init_interpreter.cpp create mode 100644 xo-interpreter/utest/CMakeLists.txt create mode 100644 xo-interpreter/utest/LocalEnv.test.cpp create mode 100644 xo-interpreter/utest/interpreter_utest_main.cpp diff --git a/xo-interpreter/.gitrepo b/xo-interpreter/.gitrepo new file mode 100644 index 00000000..894c9bca --- /dev/null +++ b/xo-interpreter/.gitrepo @@ -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-interpreter.git + branch = main + commit = 047658a5b367705afe69422a3409c7c068e2a5fb + parent = b82663b75d776208cc0921c697475c202fe043ab + method = merge + cmdver = 0.4.9 diff --git a/xo-interpreter/CMakeLists.txt b/xo-interpreter/CMakeLists.txt new file mode 100644 index 00000000..a14ee394 --- /dev/null +++ b/xo-interpreter/CMakeLists.txt @@ -0,0 +1,41 @@ +# 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 diff --git a/xo-interpreter/README.md b/xo-interpreter/README.md new file mode 100644 index 00000000..133805c0 --- /dev/null +++ b/xo-interpreter/README.md @@ -0,0 +1 @@ +# xo-interpreter diff --git a/xo-interpreter/cmake/xo-bootstrap-macros.cmake b/xo-interpreter/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..592272c0 --- /dev/null +++ b/xo-interpreter/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,41 @@ +# ---------------------------------------------------------------- +# 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() diff --git a/xo-interpreter/cmake/xo_interpreterConfig.cmake.in b/xo-interpreter/cmake/xo_interpreterConfig.cmake.in new file mode 100644 index 00000000..a1dba569 --- /dev/null +++ b/xo-interpreter/cmake/xo_interpreterConfig.cmake.in @@ -0,0 +1,8 @@ +@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@") diff --git a/xo-interpreter/docs/CMakeLists.txt b/xo-interpreter/docs/CMakeLists.txt new file mode 100644 index 00000000..b9df2dd6 --- /dev/null +++ b/xo-interpreter/docs/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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 diff --git a/xo-interpreter/docs/README b/xo-interpreter/docs/README new file mode 100644 index 00000000..6aff5d41 --- /dev/null +++ b/xo-interpreter/docs/README @@ -0,0 +1,41 @@ +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 diff --git a/xo-interpreter/docs/_static/README b/xo-interpreter/docs/_static/README new file mode 100644 index 00000000..8230095c --- /dev/null +++ b/xo-interpreter/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here \ No newline at end of file diff --git a/xo-interpreter/docs/_static/img/favicon.ico b/xo-interpreter/docs/_static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4163dd69c734f8186cf1ce5e726213cebd231c31 GIT binary patch literal 309936 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 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' diff --git a/xo-interpreter/docs/index.rst b/xo-interpreter/docs/index.rst new file mode 100644 index 00000000..e5b5a1c2 --- /dev/null +++ b/xo-interpreter/docs/index.rst @@ -0,0 +1,12 @@ +.. 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 diff --git a/xo-interpreter/docs/install.rst b/xo-interpreter/docs/install.rst new file mode 100644 index 00000000..827bb1b0 --- /dev/null +++ b/xo-interpreter/docs/install.rst @@ -0,0 +1,202 @@ +.. _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) diff --git a/xo-interpreter/example/CMakeLists.txt b/xo-interpreter/example/CMakeLists.txt new file mode 100644 index 00000000..ce7aaf20 --- /dev/null +++ b/xo-interpreter/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(replxx) diff --git a/xo-interpreter/example/replxx/CMakeLists.txt b/xo-interpreter/example/replxx/CMakeLists.txt new file mode 100644 index 00000000..96f11ae4 --- /dev/null +++ b/xo-interpreter/example/replxx/CMakeLists.txt @@ -0,0 +1,17 @@ +# 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 diff --git a/xo-interpreter/example/replxx/replxx.cpp b/xo-interpreter/example/replxx/replxx.cpp new file mode 100644 index 00000000..ef677594 --- /dev/null +++ b/xo-interpreter/example/replxx/replxx.cpp @@ -0,0 +1,19 @@ +/** @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 */ diff --git a/xo-interpreter/include/xo/interpreter/BuiltinPrimitives.hpp b/xo-interpreter/include/xo/interpreter/BuiltinPrimitives.hpp new file mode 100644 index 00000000..39995b23 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/BuiltinPrimitives.hpp @@ -0,0 +1,36 @@ +/** @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 + static void install_pm(gc::IAlloc * mm, rp pm_expr, gp env) { + gp rhs + = xo::obj::make_primitive(mm, pm_expr->name(), pm_expr->value()); + + /* store in env using this variable-expr */ + rp lhs + = Variable::make(pm_expr->name(), pm_expr->value_td()); + + gp * addr = env->establish_var(lhs.borrow()); + + *addr = rhs; + } + + static void install(gc::IAlloc * mm, gp env); + }; + } +} + +/* end BuiltinPrimitives.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/Env.hpp b/xo-interpreter/include/xo/interpreter/Env.hpp new file mode 100644 index 00000000..bce51319 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/Env.hpp @@ -0,0 +1,47 @@ +/** @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 * 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 * establish_var(bp v) = 0; + + //gp lookup_symbol(const std::string & name) const; + }; + } /*namespace scm*/ +} /*namespace xo*/ diff --git a/xo-interpreter/include/xo/interpreter/ExpressionBoxed.hpp b/xo-interpreter/include/xo/interpreter/ExpressionBoxed.hpp new file mode 100644 index 00000000..5668a08a --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/ExpressionBoxed.hpp @@ -0,0 +1,48 @@ +/** @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 c); + + /** create boxed version of @p c, using allocator @p mm **/ + static gp make(gc::IAlloc * mm, + bp c); + + /** runtime downcast **/ + static gp from(gp x) { + return gp::from(x); + } + + const rp & 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 contents_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end ExpressionBoxed.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp new file mode 100644 index 00000000..50e765ba --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp @@ -0,0 +1,73 @@ +/** @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>; + + public: + /** Create top-level global environment, allocating via @p mm. + * Expect one of these per interpreter session. + **/ + static gp make_empty(gc::IAlloc * mm, + const rp & symtab); + +#ifdef NOT_USING + gc::IAlloc * get_mm() const { return mm_; } +#endif + + const rp & symtab() const { return symtab_; } + + // inherited from Env.. + virtual bool local_contains_var(const std::string & vname) const final override; + virtual gp * lookup_slot(const std::string & vname) final override; + virtual gp * establish_var(bp 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 & 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 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 slot_map_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end GlobalEnv.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/LocalEnv.hpp b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp new file mode 100644 index 00000000..9745a854 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp @@ -0,0 +1,95 @@ +/** @file LocalEnv.hpp **/ + +#include "Env.hpp" +#include "CVector.hpp" +#include "xo/allocutil/IAlloc.hpp" +#include "xo/expression/LocalSymtab.hpp" +#include +#include + +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 p, const rp & 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 make(gc::IAlloc * mm, + gp p, + const rp & s, + std::size_t n_slot); + + /** reflect LocalEnv object representation **/ + static void reflect_self(); + + gp parent() const { return parent_; } + std::size_t size() const { return slot_v_.size(); } + + gp operator[](std::size_t i) const { return slot_v_[i]; } + gp & 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 * 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 * establish_var(bp 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 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 symtab_; + /** environment contents **/ + obj::CVector> slot_v_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end LocalEnv.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/Schematika.hpp b/xo-interpreter/include/xo/interpreter/Schematika.hpp new file mode 100644 index 00000000..0c02d974 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/Schematika.hpp @@ -0,0 +1,70 @@ +/** @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- backwards delete word + * - meta-p| retrieve previous command from history + * - meta-n| retrieve next command from history + * - / 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 p_impl_; + }; + } +} + +/* end Schematika.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/SchematikaError.hpp b/xo-interpreter/include/xo/interpreter/SchematikaError.hpp new file mode 100644 index 00000000..6baabad3 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/SchematikaError.hpp @@ -0,0 +1,28 @@ +/** @file SchematikaError.hpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#pragma once + +#include + +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 */ diff --git a/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp b/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp new file mode 100644 index 00000000..a8a182d9 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp @@ -0,0 +1,187 @@ +/** @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 env, + log_level log_level); + + /** memory allocator for interpreter operation. **/ + gc::IAlloc * object_mm_ = nullptr; + /** global environment **/ + gp 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 toplevel_env, log_level log_level); + ~VirtualSchematikaMachine(); + + gp 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, SchematikaError> eval(bp expr, gp env); + + /** evaluate expression @p expr in toplevel environment **/ + std::pair, SchematikaError> toplevel_eval(bp 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 expr_; + /** holds bindings for all schematika variables, to drive @ref execute_one. + * execute_one will not save this + * + * caller saves! + **/ + gp env_; + + /** vsm stack. callee saves! + **/ + gp stack_; + + /** non-error result value from eval() / apply() + * + * output register: caller must save + **/ + gp 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 */ diff --git a/xo-interpreter/include/xo/interpreter/VsmInstr.hpp b/xo-interpreter/include/xo/interpreter/VsmInstr.hpp new file mode 100644 index 00000000..a1539bd0 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/VsmInstr.hpp @@ -0,0 +1,80 @@ +/** @file VsmInstr.hpp **/ + +#pragma once + +#include + +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 */ diff --git a/xo-interpreter/include/xo/interpreter/VsmStackFrame.hpp b/xo-interpreter/include/xo/interpreter/VsmStackFrame.hpp new file mode 100644 index 00000000..0fc0dee7 --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/VsmStackFrame.hpp @@ -0,0 +1,83 @@ +/** @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 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 make(gc::IAlloc * mm, + gp 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 push1(gc::IAlloc * mm, + gp p, + gp 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 push2(gc::IAlloc * mm, + gp p, + gp s0, + gp s1, + const VsmInstr * cont); + + + /** reflect VsmStackFrame object representation **/ + static void reflect_self(); + + gp parent() const { return parent_; } + std::size_t size() const { return slot_v_.size(); } + const obj::CVector> & argv() const { return slot_v_; } + const VsmInstr * continuation() const { return cont_; } + + gp operator[](std::size_t i) const { return slot_v_[i]; } + gp & 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 parent_; + + /** stored state **/ + obj::CVector> slot_v_; + + /** proceed to this continuation when popping this frame **/ + const VsmInstr * cont_ = nullptr; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end VsmStackFrame.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/init_interpreter.hpp b/xo-interpreter/include/xo/interpreter/init_interpreter.hpp new file mode 100644 index 00000000..91e4828c --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/init_interpreter.hpp @@ -0,0 +1,21 @@ +/** @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 { + static void init(); + static InitEvidence require(); + }; +} + +/* end init_interpreter.hpp */ diff --git a/xo-interpreter/src/interpreter/BuiltinPrimitives.cpp b/xo-interpreter/src/interpreter/BuiltinPrimitives.cpp new file mode 100644 index 00000000..bead7c87 --- /dev/null +++ b/xo-interpreter/src/interpreter/BuiltinPrimitives.cpp @@ -0,0 +1,94 @@ +/** @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 + +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 env) + { + scope log(XO_DEBUG(true)); + + // add(x,y) + { + gp rhs = xo::obj::make_primitive(mm, "add", add64); + + TypeDescr td = Reflect::require_function(); + + rp lhs = Variable::make("add", td); + gp * 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 */ diff --git a/xo-interpreter/src/interpreter/CMakeLists.txt b/xo-interpreter/src/interpreter/CMakeLists.txt new file mode 100644 index 00000000..912d4a93 --- /dev/null +++ b/xo-interpreter/src/interpreter/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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) diff --git a/xo-interpreter/src/interpreter/ExpressionBoxed.cpp b/xo-interpreter/src/interpreter/ExpressionBoxed.cpp new file mode 100644 index 00000000..2d72ef33 --- /dev/null +++ b/xo-interpreter/src/interpreter/ExpressionBoxed.cpp @@ -0,0 +1,59 @@ +/** @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 c) : contents_{c.promote()} + {} + + gp + ExpressionBoxed::make(gc::IAlloc * mm, + bp c) + { + return new (MMPtr(mm)) ExpressionBoxed(c); + } + + + TaggedPtr + ExpressionBoxed::self_tp() const + { + return Reflect::make_tp(const_cast(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 */ diff --git a/xo-interpreter/src/interpreter/GlobalEnv.cpp b/xo-interpreter/src/interpreter/GlobalEnv.cpp new file mode 100644 index 00000000..736af083 --- /dev/null +++ b/xo-interpreter/src/interpreter/GlobalEnv.cpp @@ -0,0 +1,127 @@ +/** @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::make_empty(gc::IAlloc * mm, const rp & 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(*x.slot_map_)} + { + } + + GlobalEnv::GlobalEnv(gc::IAlloc * mm, + const rp & symtab) : mm_{mm}, + symtab_{symtab}, + slot_map_{std::make_unique()} + {} + + bool + GlobalEnv::local_contains_var(const std::string & vname) const + { + return symtab_->lookup_local(vname).get(); + } + + gp * + 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 * + GlobalEnv::establish_var(bp 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 &slot = (*this->slot_map_)[var->name()]; + + /* discard any pre-existing value, we're redefining a variable */ + slot = gp(); + + return &slot; + } + + TaggedPtr + GlobalEnv::self_tp() const + { + return Reflect::make_tp(const_cast(this)); + } + + void + GlobalEnv::display(std::ostream & os) const + { + os << "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 */ diff --git a/xo-interpreter/src/interpreter/LocalEnv.cpp b/xo-interpreter/src/interpreter/LocalEnv.cpp new file mode 100644 index 00000000..8085f126 --- /dev/null +++ b/xo-interpreter/src/interpreter/LocalEnv.cpp @@ -0,0 +1,185 @@ +/** @file LocalEnv.cpp **/ + +#include "LocalEnv.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include + +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); + } + } + + gp + LocalEnv::make(gc::IAlloc * mm, + gp p, + const rp & s, + std::size_t n) + { + if (s) { + assert(static_cast(n) == s->n_arg()); + } + + return new (MMPtr(mm)) LocalEnv(mm, p, s, n); + } + + LocalEnv::LocalEnv(gc::IAlloc * mm, + gp p, + const rp & 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 * + 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(b.j_slot_) < slot_v_.size())); + + return &(slot_v_[b.j_slot_]); + } + + if (parent_.get()) { + return parent_->lookup_slot(vname); + } + + return nullptr; + } + + gp * + LocalEnv::establish_var(bp 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(this)); + } + + void + LocalEnv::display(std::ostream & os) const + { + 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 sr; + + if (sr.is_incomplete()) { + /* reflect CVector> + * + * note: placement here works b/c CVector not used anywhere else + */ + using VectorType = obj::CVector>; + + /* custom reflection for array of Object pointers. + * Can use StlVectorTdx here, treating CVector as a vector + * via .size() and .operator[] members + */ + std::unique_ptr tdx1 + = std::make_unique>(); + TypeDescrW td1 + = EstablishTypeDescr::establish(); + td1->assign_tdextra(Reflect::get_final_invoker(), + std::move(tdx1)); + + REFLECT_MEMBER(sr, parent); + REFLECT_MEMBER(sr, slot_v); + } + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end LocalEnv.cpp */ diff --git a/xo-interpreter/src/interpreter/Schematika.cpp b/xo-interpreter/src/interpreter/Schematika.cpp new file mode 100644 index 00000000..039b2e67 --- /dev/null +++ b/xo-interpreter/src/interpreter/Schematika.cpp @@ -0,0 +1,284 @@ +/** @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 +#include +#include // 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 mm, gp toplevel_env); + ~Impl(); + + /** create instance + allocator **/ + static up 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 mm_; + /** schematika interpreter **/ + VirtualSchematikaMachine vsm_; + }; + + Schematika::Impl::Impl(const Config & config, up mm, gp toplevel_env) : + config_{config}, + mm_{std::move(mm)}, + vsm_{mm_.get(), toplevel_env, config.vsm_log_level_} + { + + } + + Schematika::Impl::~Impl() = default; + + up + Schematika::Impl::make(const Config & cfg) + { + up mm = GC::make(cfg.gc_config_); + rp symtab = GlobalSymtab::make_empty(); + gp 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(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- backward delete word" << endl; + os << " |meta-p previous command from history" << endl; + os << " |meta-n next command from history" << endl; + os << " / 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; + + 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 if non-null: the next expression from input + * consumed :: span 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(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 */ diff --git a/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp b/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp new file mode 100644 index 00000000..25f620b0 --- /dev/null +++ b/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp @@ -0,0 +1,865 @@ +/** @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 env, + log_level ll) : + object_mm_{mm}, + toplevel_env_{env}, + log_level_{ll} + { + } + + // ----- VirtualSchematikaMachine ----- + + VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm, + gp 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, + SchematikaError> + VirtualSchematikaMachine::toplevel_eval(bp expr) + { + return this->eval(expr, this->env_); + } + + std::pair, + SchematikaError> + VirtualSchematikaMachine::eval(bp expr, gp 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 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 expr = PrimitiveExprInterface::from(expr_); + + const gp * 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 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 -> gp **/ + gp 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 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 * slot = env_->lookup_slot(assign->lhs()->name()); + + if (slot) { + /** must promote rp -> gp **/ + gp 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 sp0 = this->stack_; + + bp var = Variable::from(ExpressionBoxed::from((*sp0)[0])->contents()); + + assert(var.get()); + + gp * 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 var = Variable::from(expr_); + + assert(var.get()); + assert(env_.get()); + + const gp * 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 -> gp **/ + gp ifexpr_boxed = ExpressionBoxed::make(mm, expr_); + bp 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 test_value = gp::from(value_); + + if (test_value.get()) { + gp sp0 = this->stack_; + + bp 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::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 -> gp **/ + gp seq_boxed = ExpressionBoxed::make(mm, expr_); + bp 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::make(false); + } else { + this->expr_ = (*seq)[0]; + } + + if (seq->size() > 1) { + /* remainder */ + + gp 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 sp0 = this->stack_; + + assert(sp0->size() == 2); + + bp seq = Sequence::from(ExpressionBoxed::from((*sp0)[0])->contents()); + gp 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 -> gp **/ + gp apply_boxed = ExpressionBoxed::make(mm, expr_); + bp apply = Apply::from(expr_); + + assert(apply.get()); + + size_t n = apply->n_arg() + 1; + + /* reminder: argument 0 refers to the function being called */ + gp targetarg = Integer::make(mm, 0); + + /* outer frame */ + gp argstack = VsmStackFrame::make(mm, stack_, n, cont_); + + /* scratch frame during call sequence. + * probably collect->cont_ will not be used? + */ + gp 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 sp0 = this->stack_; + + assert(sp0.get()); + assert(sp0->size() == 2); + + bp apply = Apply::from(ExpressionBoxed::from((*sp0)[0])->contents()); + assert(apply.get()); + gp 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 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 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 sp0 = stack_; + + assert(sp0->size() > 0); + + gp 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 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 */ diff --git a/xo-interpreter/src/interpreter/VsmInstr.cpp b/xo-interpreter/src/interpreter/VsmInstr.cpp new file mode 100644 index 00000000..3ca138d9 --- /dev/null +++ b/xo-interpreter/src/interpreter/VsmInstr.cpp @@ -0,0 +1,16 @@ +/** @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 */ diff --git a/xo-interpreter/src/interpreter/VsmStackFrame.cpp b/xo-interpreter/src/interpreter/VsmStackFrame.cpp new file mode 100644 index 00000000..9bca575e --- /dev/null +++ b/xo-interpreter/src/interpreter/VsmStackFrame.cpp @@ -0,0 +1,177 @@ +/** @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); + } + } + + VsmStackFrame::VsmStackFrame(gc::IAlloc * mm, + gp p, + std::size_t n, + const VsmInstr * cont) : parent_{p}, + slot_v_{mm, n}, + cont_{cont} + {} + + gp + VsmStackFrame::make(gc::IAlloc * mm, + gp p, + std::size_t n, + const VsmInstr * cont) + { + gp 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::push1(gc::IAlloc * mm, + gp p, + gp s0, + const VsmInstr * cont) + { + gp retval = new (MMPtr(mm)) VsmStackFrame(mm, p, 1, cont); + + (*retval)[0] = s0; + + return retval; + } + + gp + VsmStackFrame::push2(gc::IAlloc * mm, + gp p, + gp s0, + gp s1, + const VsmInstr * cont) + { + gp 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(this)); + } + + void + VsmStackFrame::display(std::ostream & os) const + { + 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 sr; + + if (sr.is_incomplete() ) { + /* reflect CVector>. + * duplicates similar code in LocalEnv::reflect_self() + */ + using VectorType = obj::CVector>; + + /* custom reflection for array of Object pointers. + * Can use StlVectorTdx here, treating CVector as a vector + * via .size() and .operator[] members + */ + std::unique_ptr tdx1 + = std::make_unique>(); + TypeDescrW td1 + = EstablishTypeDescr::establish(); + td1->assign_tdextra(Reflect::get_final_invoker(), + std::move(tdx1)); + + REFLECT_MEMBER(sr, parent); + REFLECT_MEMBER(sr, slot_v); + } + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end VsmStackFrame.cpp */ diff --git a/xo-interpreter/src/interpreter/init_interpreter.cpp b/xo-interpreter/src/interpreter/init_interpreter.cpp new file mode 100644 index 00000000..eaa0bffe --- /dev/null +++ b/xo-interpreter/src/interpreter/init_interpreter.cpp @@ -0,0 +1,27 @@ +/** @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::init() + { + LocalEnv::reflect_self(); + } + + InitEvidence + InitSubsys::require() + { + return Subsystem::provide("interpreter", &init); + } + +} /*namespace xo*/ + +/* end init_interpreter.cpp */ diff --git a/xo-interpreter/utest/CMakeLists.txt b/xo-interpreter/utest/CMakeLists.txt new file mode 100644 index 00000000..01cef051 --- /dev/null +++ b/xo-interpreter/utest/CMakeLists.txt @@ -0,0 +1,12 @@ +# 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) diff --git a/xo-interpreter/utest/LocalEnv.test.cpp b/xo-interpreter/utest/LocalEnv.test.cpp new file mode 100644 index 00000000..084d5a4d --- /dev/null +++ b/xo-interpreter/utest/LocalEnv.test.cpp @@ -0,0 +1,134 @@ +/** @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 +#include +#include + +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::require()); + + namespace { + struct Testcase_LocalEnv { + Testcase_LocalEnv(const std::vector & contents) : contents_{contents} {} + + /* build xo::obj::Integer for each contents_[i], store in F[i] for new LocalEnv F */ + std::vector contents_; + }; + + std::vector + 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 alloc = ArenaAlloc::make("utest", 16384, c_debug_flag); + REQUIRE(alloc.get()); + Object::mm = alloc.get(); + + std::size_t n = tc.contents_.size(); + gp 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::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 x = Integer::make(gc.get(), 42); + gc->add_gc_root(reinterpret_cast(&x)); + REQUIRE(gc->tospace_generation_of(x.ptr()) == generation_result::nursery); + + gp frame = LocalEnv::make(gc.get(), nullptr /*parent*/, nullptr /*symtab*/, n); + LocalEnv ** frame_pp = frame.ptr_address(); + gc->add_gc_root(reinterpret_cast(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)); + + 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 */ diff --git a/xo-interpreter/utest/interpreter_utest_main.cpp b/xo-interpreter/utest/interpreter_utest_main.cpp new file mode 100644 index 00000000..e385f9b4 --- /dev/null +++ b/xo-interpreter/utest/interpreter_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file interpreter_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end interpreter_utest_main.cpp */