Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions core/include/balsa/lua/lua_repl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#if !defined(BALSA_LUA_LUA_REPL_HPP)
#define BALSA_LUA_LUA_REPL_HPP

// LuaRepl — lightweight Lua REPL engine.
//
// Owns a sol::state, captures print() output, maintains command
// history, and provides execute() for evaluating Lua code.
// No UI dependency — pure engine. Used by both ImGui and Qt
// frontends in the visualization layer.

#include <functional>
#include <memory>
#include <string>
#include <vector>

// Forward-declare sol types to avoid leaking sol2 into public headers.
namespace sol {
class state;
}

namespace balsa::lua {

class LuaRepl {
public:
LuaRepl();
~LuaRepl();

// Non-copyable, movable.
LuaRepl(const LuaRepl &) = delete;
auto operator=(const LuaRepl &) -> LuaRepl & = delete;
LuaRepl(LuaRepl &&) noexcept;
auto operator=(LuaRepl &&) noexcept -> LuaRepl &;

// ── Execution ───────────────────────────────────────────────────

// Execute a line of Lua code. Returns true if the execution
// succeeded, false on error. Both output and errors are
// appended to the output buffer.
auto execute(const std::string &code) -> bool;

// ── Output buffer ───────────────────────────────────────────────
//
// All print() output, error messages, and echoed input accumulate
// here. The UI reads this buffer for display.

auto output() const -> const std::string & { return _output; }
auto clear_output() -> void { _output.clear(); }

// ── Command history ─────────────────────────────────────────────

auto history() const -> const std::vector<std::string> & { return _history; }

// ── Post-execute callback ───────────────────────────────────────
//
// Called after each successful execute(). The scene wiring layer
// uses this to trigger MeshData::rediscover_attributes() etc.

using PostExecuteCallback = std::function<void()>;
auto set_post_execute_callback(PostExecuteCallback cb) -> void {
_post_execute_cb = std::move(cb);
}

// ── Direct state access ─────────────────────────────────────────
//
// For binding registration (quiver::lua::load_bindings,
// balsa::geometry::lua::load_bindings, etc.)

auto lua_state() -> sol::state &;

private:
struct Impl;
std::unique_ptr<Impl> _impl;

std::string _output;
std::vector<std::string> _history;
PostExecuteCallback _post_execute_cb;
};

} // namespace balsa::lua

#endif // BALSA_LUA_LUA_REPL_HPP
5 changes: 5 additions & 0 deletions core/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ if get_option('json')
core_required_deps += nlohmann_json_dep
endif

if get_option('lua')
core_required_deps += lua_deps
core_sources += 'src/lua/lua_repl.cpp'
endif

core_lib = library('balsaCore', core_sources, include_directories: include_dir, dependencies: core_required_deps)

core_dep = declare_dependency(link_with: core_lib, dependencies: core_required_deps, include_directories: include_dir)
Expand Down
109 changes: 109 additions & 0 deletions core/src/lua/lua_repl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "balsa/lua/lua_repl.hpp"

#include <spdlog/spdlog.h>

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

namespace balsa::lua {

// ── Pimpl ───────────────────────────────────────────────────────────

struct LuaRepl::Impl {
sol::state lua;
};

// ── Construction / destruction ──────────────────────────────────────

LuaRepl::LuaRepl() : _impl(std::make_unique<Impl>()) {
// Open standard Lua libraries.
_impl->lua.open_libraries(sol::lib::base,
sol::lib::string,
sol::lib::table,
sol::lib::math,
sol::lib::io,
sol::lib::os,
sol::lib::package);

// Redirect print() to our output buffer.
_impl->lua.set_function("print", [this](sol::variadic_args va) {
bool first = true;
for (auto arg : va) {
if (!first) _output += '\t';
first = false;

// Use Lua's tostring() for consistent formatting.
sol::object obj = arg;
auto ts = _impl->lua["tostring"];
if (ts.valid()) {
sol::protected_function_result r = ts(obj);
if (r.valid()) {
std::string s = r.get<std::string>();
_output += s;
} else {
_output += "<tostring error>";
}
} else {
_output += "<no tostring>";
}
}
_output += '\n';
});
}

LuaRepl::~LuaRepl() = default;

LuaRepl::LuaRepl(LuaRepl &&) noexcept = default;
auto LuaRepl::operator=(LuaRepl &&) noexcept -> LuaRepl & = default;

// ── Execution ───────────────────────────────────────────────────────

auto LuaRepl::execute(const std::string &code) -> bool {
if (code.empty()) return true;

// Record in history.
_history.push_back(code);

// Echo the input.
_output += "> " + code + '\n';

// Try as expression first (return value), then as statement.
// This mimics standard REPL behavior: "1+2" prints 3.
std::string expr_code = "return " + code;
auto result = _impl->lua.safe_script(expr_code, sol::script_pass_on_error);

if (!result.valid()) {
// Not a valid expression — try as a statement.
result = _impl->lua.safe_script(code, sol::script_pass_on_error);
}

if (!result.valid()) {
sol::error err = result;
_output += std::string("[error] ") + err.what() + '\n';
return false;
}

// If the expression returned a value, print it.
if (result.return_count() > 0) {
sol::object val = result;
if (val.valid() && val.get_type() != sol::type::none
&& val.get_type() != sol::type::lua_nil) {
auto ts = _impl->lua["tostring"];
if (ts.valid()) {
sol::protected_function_result r = ts(val);
if (r.valid()) { _output += r.get<std::string>() + '\n'; }
}
}
}

// Invoke post-execute callback.
if (_post_execute_cb) { _post_execute_cb(); }

return true;
}

// ── Direct state access ─────────────────────────────────────────────

auto LuaRepl::lua_state() -> sol::state & { return _impl->lua; }

} // namespace balsa::lua
22 changes: 22 additions & 0 deletions geometry/include/balsa/geometry/lua/bindings.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if !defined(BALSA_GEOMETRY_LUA_BINDINGS_HPP)
#define BALSA_GEOMETRY_LUA_BINDINGS_HPP

// Geometry Lua bindings — registers balsa::geometry operations into
// a sol::state_view under the "geometry" table.
//
// Requires quiver Lua bindings to be loaded first (mesh types must
// be registered).

namespace sol {
class state_view;
}

namespace balsa::geometry::lua {

// Register geometry bindings (bounding_box, volumes, read_obj, etc.)
// into the given Lua state. Creates a "geometry" table.
auto load_bindings(sol::state_view lua) -> void;

} // namespace balsa::geometry::lua

#endif // BALSA_GEOMETRY_LUA_BINDINGS_HPP
5 changes: 5 additions & 0 deletions geometry/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ if get_option('quiver')
geometry_required_deps += quiver_dep
endif

if get_option('lua')
geometry_required_deps += lua_deps
geometry_sources += 'src/lua/geometry_bindings.cpp'
endif

include_dir = [include_directories('include')]

geometry_lib = library('balsaGeometry', geometry_sources, include_directories: include_dir, dependencies: geometry_required_deps)
Expand Down
Loading
Loading