File diff suppressed because one or more lines are too long
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1751011381,
|
||||
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
|
||||
"lastModified": 1752950548,
|
||||
"narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
|
||||
"rev": "c87b95e25065c028d31a94f06a62927d18763fdf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
109
src/Config.cppm
Normal file
109
src/Config.cppm
Normal file
@@ -0,0 +1,109 @@
|
||||
module;
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#include "dhos_config.h"
|
||||
|
||||
export module LunarWM.Config;
|
||||
|
||||
import std;
|
||||
|
||||
static auto default_configuration_paths()
|
||||
-> std::vector<std::filesystem::path> const
|
||||
{
|
||||
std::vector<std::filesystem::path> paths = {
|
||||
"lunarwm/lunarwm.dcfg",
|
||||
"lunarwm.dcfg",
|
||||
"/etc/lunarwm.dcfg",
|
||||
"/etc/lunarwm/lunarwm.dcfg",
|
||||
};
|
||||
|
||||
if (char const *xdg_dirs = std::getenv("XDG_CONFIG_DIRS")) {
|
||||
std::string const dirs { xdg_dirs };
|
||||
std::istringstream ss { dirs };
|
||||
std::string dir;
|
||||
while (std::getline(ss, dir, ':')) {
|
||||
if (!dir.empty()) {
|
||||
paths.push_back(
|
||||
std::filesystem::path(dir) / "lunarwm/lunarwm.dcfg");
|
||||
paths.push_back(std::filesystem::path(dir) / "lunarwm.dcfg");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (char const *home = std::getenv("HOME")) {
|
||||
std::filesystem::path const home_path(home);
|
||||
paths.push_back(home_path / ".config/lunarwm/lunarwm.dcfg");
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export namespace LunarWM::Config {
|
||||
|
||||
struct Configuration {
|
||||
struct Keybind {
|
||||
uint32_t modifiers;
|
||||
xkb_keysym_t sym;
|
||||
dhos::Value action;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct {
|
||||
std::vector<std::string> xkb_options;
|
||||
} keyboard {};
|
||||
} input {};
|
||||
|
||||
std::vector<Keybind> keybindings;
|
||||
|
||||
dhos::Object custom_lib;
|
||||
|
||||
void execute_keybind(Keybind const &keybind) {
|
||||
dhos::eval(keybind.action, &this->custom_lib);
|
||||
}
|
||||
|
||||
void add_to_custom_lib(std::string &name, dhos::Builtin fn) {
|
||||
this->custom_lib[name] = dhos::Value{dhos::Builtin{fn}};
|
||||
}
|
||||
|
||||
void load() { this->load(default_configuration_paths()); }
|
||||
void load(std::vector<std::filesystem::path> const &paths);
|
||||
};
|
||||
|
||||
void Configuration::load(std::vector<std::filesystem::path> const &paths)
|
||||
{
|
||||
for (auto const &path : paths) {
|
||||
try {
|
||||
auto e = dhos::eval(dhos::parse_config(path), this->custom_lib);
|
||||
if (!e.is_obj()) {
|
||||
throw std::runtime_error("Top level is not an object!");
|
||||
}
|
||||
|
||||
if (e["input"].is_obj()) {
|
||||
auto const &input = e["input"];
|
||||
if (input["keyboard"].is_obj()) {
|
||||
auto const &keyboard = input["keyboard"];
|
||||
if (keyboard["xkb_options"].is_arr()) {
|
||||
auto const &arr = keyboard["xkb_options"].arr();
|
||||
this->input.keyboard.xkb_options.clear();
|
||||
for (auto const &value : arr) {
|
||||
if (value.is_str()) {
|
||||
this->input.keyboard.xkb_options.push_back(
|
||||
value.str());
|
||||
} else {
|
||||
// FIXME: Send warning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::filesystem::filesystem_error const &e) {
|
||||
(void)e;
|
||||
} catch (std::exception &e) {
|
||||
(void)e;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Unable to find a valid configuration file!");
|
||||
}
|
||||
|
||||
} // LunarWM::Config
|
||||
150
src/LunarWM.cppm
150
src/LunarWM.cppm
@@ -19,8 +19,8 @@ module;
|
||||
#include <wayland-server-core.h>
|
||||
|
||||
extern "C" {
|
||||
#include <wlr/backend/wayland.h>
|
||||
#include <wlr/backend/session.h>
|
||||
#include <wlr/backend/wayland.h>
|
||||
#include <wlr/render/allocator.h>
|
||||
#include <wlr/render/egl.h>
|
||||
#include <wlr/render/gles2.h>
|
||||
@@ -44,6 +44,7 @@ import std;
|
||||
|
||||
import LunarWM.Math;
|
||||
import LunarWM.Util;
|
||||
import LunarWM.Config;
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
@@ -305,11 +306,14 @@ private:
|
||||
|
||||
// Extensions
|
||||
struct Hand {
|
||||
std::array<XrHandJointLocationEXT, XR_HAND_JOINT_COUNT_EXT> joint_locations {};
|
||||
std::array<XrHandJointLocationEXT, XR_HAND_JOINT_COUNT_EXT>
|
||||
joint_locations {};
|
||||
XrHandTrackerEXT hand_tracker {};
|
||||
};
|
||||
std::array<Hand, 2> hands;
|
||||
XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties {XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT};
|
||||
XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties {
|
||||
XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT
|
||||
};
|
||||
|
||||
// Extension functions
|
||||
PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT {};
|
||||
@@ -433,6 +437,8 @@ private:
|
||||
|
||||
std::chrono::time_point<Clock> m_last_tick;
|
||||
|
||||
Config::Configuration config {};
|
||||
|
||||
bool m_running {};
|
||||
bool m_session_running {};
|
||||
bool m_session_state {};
|
||||
@@ -440,9 +446,10 @@ private:
|
||||
|
||||
void LunarWM::init()
|
||||
{
|
||||
//if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { // NOLINT
|
||||
// throw std::runtime_error("This compositor can only be ran in DRM mode");
|
||||
//}
|
||||
// if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr)
|
||||
// { // NOLINT throw std::runtime_error("This compositor can only be ran in
|
||||
// DRM mode");
|
||||
// }
|
||||
|
||||
this->init_wayland();
|
||||
|
||||
@@ -556,9 +563,11 @@ void LunarWM::init_wayland()
|
||||
keyboard->server = wm;
|
||||
keyboard->wlr_keyboard = wlr_keyboard;
|
||||
|
||||
struct xkb_rule_names const rule_names {};
|
||||
|
||||
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
struct xkb_keymap *keymap = xkb_keymap_new_from_names(
|
||||
context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
|
||||
wlr_keyboard_set_keymap(wlr_keyboard, keymap);
|
||||
xkb_keymap_unref(keymap);
|
||||
@@ -584,15 +593,19 @@ void LunarWM::init_wayland()
|
||||
struct wlr_seat *seat = server->m_wayland.seat;
|
||||
|
||||
uint32_t const keycode = event->keycode + 8;
|
||||
xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode);
|
||||
xkb_keysym_t const *syms = nullptr;
|
||||
// int const nsyms = xkb_state_key_get_syms(
|
||||
// kbd->wlr_keyboard->xkb_state, keycode, &syms);
|
||||
int const nsyms = xkb_state_key_get_syms(
|
||||
kbd->wlr_keyboard->xkb_state, keycode, &syms);
|
||||
xkb_keysym_t const keysym = xkb_state_key_get_one_sym(
|
||||
kbd->wlr_keyboard->xkb_state, keycode);
|
||||
|
||||
bool const handled = false;
|
||||
uint32_t const modifiers
|
||||
= wlr_keyboard_get_modifiers(kbd->wlr_keyboard);
|
||||
if (server->m_wayland.session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) {
|
||||
if (server->m_wayland.session
|
||||
&& event->state == WL_KEYBOARD_KEY_STATE_PRESSED
|
||||
&& keysym >= XKB_KEY_XF86Switch_VT_1
|
||||
&& keysym <= XKB_KEY_XF86Switch_VT_12) {
|
||||
unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
|
||||
wlr_session_change_vt(server->m_wayland.session, vt);
|
||||
return;
|
||||
@@ -603,7 +616,6 @@ void LunarWM::init_wayland()
|
||||
if (syms[XKB_KEY_Escape]) {
|
||||
kbd->server->terminate();
|
||||
}
|
||||
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
@@ -784,17 +796,24 @@ void LunarWM::init_xr()
|
||||
}
|
||||
m_xr.instance = instance;
|
||||
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrCreateHandTrackerEXT", reinterpret_cast<PFN_xrVoidFunction*>(&m_xr.CreateHandTrackerEXT));
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrCreateHandTrackerEXT",
|
||||
reinterpret_cast<PFN_xrVoidFunction *>(&m_xr.CreateHandTrackerEXT));
|
||||
if (res != XR_SUCCESS) {
|
||||
throw std::runtime_error("Failed to get proc addr xrCreateHandTrackerEXT");
|
||||
throw std::runtime_error(
|
||||
"Failed to get proc addr xrCreateHandTrackerEXT");
|
||||
}
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrDestroyHandTrackerEXT", reinterpret_cast<PFN_xrVoidFunction*>(&m_xr.DestroyHandTrackerEXT));
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrDestroyHandTrackerEXT",
|
||||
reinterpret_cast<PFN_xrVoidFunction *>(
|
||||
&m_xr.DestroyHandTrackerEXT));
|
||||
if (res != XR_SUCCESS) {
|
||||
throw std::runtime_error("Failed to get proc addr xrDestroyHandTrackerEXT");
|
||||
throw std::runtime_error(
|
||||
"Failed to get proc addr xrDestroyHandTrackerEXT");
|
||||
}
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrLocateHandJointsEXT", reinterpret_cast<PFN_xrVoidFunction*>(&m_xr.LocateHandJointsEXT));
|
||||
res = xrGetInstanceProcAddr(*m_xr.instance, "xrLocateHandJointsEXT",
|
||||
reinterpret_cast<PFN_xrVoidFunction *>(&m_xr.LocateHandJointsEXT));
|
||||
if (res != XR_SUCCESS) {
|
||||
throw std::runtime_error("Failed to get proc addr xrLocateHandJointsEXT");
|
||||
throw std::runtime_error(
|
||||
"Failed to get proc addr xrLocateHandJointsEXT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,9 +846,11 @@ void LunarWM::init_xr()
|
||||
.type = XR_TYPE_SYSTEM_PROPERTIES,
|
||||
.next = static_cast<void *>(&m_xr.hand_tracking_system_properties),
|
||||
};
|
||||
res = xrGetSystemProperties(*m_xr.instance, *m_xr.system_id, &system_props);
|
||||
res = xrGetSystemProperties(
|
||||
*m_xr.instance, *m_xr.system_id, &system_props);
|
||||
if (res != XR_SUCCESS) {
|
||||
throw std::runtime_error(std::format("xrGetSystemProperties failed: {}", res));
|
||||
throw std::runtime_error(
|
||||
std::format("xrGetSystemProperties failed: {}", res));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1214,9 +1235,11 @@ void LunarWM::init_xr()
|
||||
.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT,
|
||||
};
|
||||
|
||||
res = m_xr.CreateHandTrackerEXT(m_xr.session, &ci, &hand.hand_tracker);
|
||||
res = m_xr.CreateHandTrackerEXT(
|
||||
m_xr.session, &ci, &hand.hand_tracker);
|
||||
if (res != XR_SUCCESS) {
|
||||
throw std::runtime_error(std::format("Failed to create hand tracker: {}", res));
|
||||
throw std::runtime_error(
|
||||
std::format("Failed to create hand tracker: {}", res));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1323,16 +1346,16 @@ void LunarWM::poll_events_xr()
|
||||
|
||||
inline constexpr std::array<std::uint8_t, 26> kHandJointMap = {
|
||||
/* 0 PALM */ 25,
|
||||
/* 1 WRIST */ 0,
|
||||
/* 2 THUMB_METACARPAL */ 1,
|
||||
/* 3 THUMB_PROXIMAL */ 2,
|
||||
/* 4 THUMB_DISTAL */ 3,
|
||||
/* 5 THUMB_TIP */ 4,
|
||||
/* 6 INDEX_METACARPAL */ 5,
|
||||
/* 7 INDEX_PROXIMAL */ 6,
|
||||
/* 8 INDEX_INTERMEDIATE */ 7,
|
||||
/* 9 INDEX_DISTAL */ 8,
|
||||
/* 10 INDEX_TIP */ 9,
|
||||
/* 1 WRIST */ 0,
|
||||
/* 2 THUMB_METACARPAL */ 1,
|
||||
/* 3 THUMB_PROXIMAL */ 2,
|
||||
/* 4 THUMB_DISTAL */ 3,
|
||||
/* 5 THUMB_TIP */ 4,
|
||||
/* 6 INDEX_METACARPAL */ 5,
|
||||
/* 7 INDEX_PROXIMAL */ 6,
|
||||
/* 8 INDEX_INTERMEDIATE */ 7,
|
||||
/* 9 INDEX_DISTAL */ 8,
|
||||
/* 10 INDEX_TIP */ 9,
|
||||
/* 11 MIDDLE_METACARPAL */ 10,
|
||||
/* 12 MIDDLE_PROXIMAL */ 11,
|
||||
/* 13 MIDDLE_INTERMEDIATE */ 12,
|
||||
@@ -1351,8 +1374,8 @@ inline constexpr std::array<std::uint8_t, 26> kHandJointMap = {
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto
|
||||
openxr_joint_to_model(std::uint32_t xrIndex) noexcept -> std::optional<std::uint8_t>
|
||||
constexpr auto static openxr_joint_to_model(std::uint32_t xrIndex) noexcept
|
||||
-> std::optional<std::uint8_t>
|
||||
{
|
||||
if (xrIndex < kHandJointMap.size()) {
|
||||
return kHandJointMap.at(xrIndex);
|
||||
@@ -1450,21 +1473,24 @@ auto LunarWM::render_layer(RenderLayerInfo &info, float dt) -> bool
|
||||
|
||||
{
|
||||
static ModelAnimation anim = {};
|
||||
if(anim.framePoses == nullptr) {
|
||||
anim.boneCount = m_renderer.hands.at(0).boneCount;
|
||||
if (anim.framePoses == nullptr) {
|
||||
anim.boneCount = m_renderer.hands.at(0).boneCount;
|
||||
anim.frameCount = 1;
|
||||
anim.bones = m_renderer.hands.at(0).bones;
|
||||
anim.framePoses = static_cast<Transform**>(MemAlloc(sizeof(Transform*)));
|
||||
anim.bones = m_renderer.hands.at(0).bones;
|
||||
anim.framePoses
|
||||
= static_cast<Transform **>(MemAlloc(sizeof(Transform *)));
|
||||
anim.framePoses[0] = // NOLINT
|
||||
static_cast<Transform*>(MemAlloc(sizeof(Transform) * anim.boneCount));
|
||||
static_cast<Transform *>(
|
||||
MemAlloc(sizeof(Transform) * anim.boneCount));
|
||||
}
|
||||
|
||||
for(int h = 0; h < 2; ++h) {
|
||||
auto &handInfo = m_xr.hands.at(h);
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
auto &handInfo = m_xr.hands.at(h);
|
||||
|
||||
for(size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
|
||||
const auto &pose = m_xr.hands.at(h).joint_locations[k].pose; // NOLINT
|
||||
const auto &jl = handInfo.joint_locations[k]; // NOLINT
|
||||
for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
|
||||
auto const &pose
|
||||
= m_xr.hands.at(h).joint_locations[k].pose; // NOLINT
|
||||
auto const &jl = handInfo.joint_locations[k]; // NOLINT
|
||||
|
||||
Vector3 const pos {
|
||||
jl.pose.position.x,
|
||||
@@ -1663,16 +1689,21 @@ void LunarWM::run()
|
||||
std::format("Failed to begin the OpenXR Frame: {}", res));
|
||||
}
|
||||
|
||||
if (static_cast<bool>(m_xr.hand_tracking_system_properties.supportsHandTracking)) {
|
||||
XrActionStateGetInfo const si { .type = XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
if (static_cast<bool>(
|
||||
m_xr.hand_tracking_system_properties.supportsHandTracking)) {
|
||||
XrActionStateGetInfo const si { .type
|
||||
= XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
for (auto &hand : m_xr.hands) {
|
||||
bool const unobstructed { true };
|
||||
|
||||
XrHandJointsMotionRangeInfoEXT mri { .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT };
|
||||
XrHandJointsMotionRangeInfoEXT mri { .type
|
||||
= XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT };
|
||||
if (unobstructed) {
|
||||
mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
|
||||
mri.handJointsMotionRange
|
||||
= XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
|
||||
} else {
|
||||
mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT;
|
||||
mri.handJointsMotionRange
|
||||
= XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT;
|
||||
}
|
||||
|
||||
XrHandJointsLocateInfoEXT const li {
|
||||
@@ -1688,7 +1719,8 @@ void LunarWM::run()
|
||||
.jointLocations = hand.joint_locations.data(),
|
||||
};
|
||||
|
||||
if (m_xr.LocateHandJointsEXT(hand.hand_tracker, &li, &hji) != XR_SUCCESS) {
|
||||
if (m_xr.LocateHandJointsEXT(hand.hand_tracker, &li, &hji)
|
||||
!= XR_SUCCESS) {
|
||||
throw std::runtime_error("Failed to locate hand joints");
|
||||
}
|
||||
}
|
||||
@@ -1763,18 +1795,16 @@ void LunarWM::render_3d(float /*dt*/)
|
||||
{
|
||||
DrawGrid(10, 1);
|
||||
|
||||
for(auto const &tl : m_wayland.toplevels) {
|
||||
for (auto const &tl : m_wayland.toplevels) {
|
||||
tl->update();
|
||||
DrawBillboardNoShear(m_renderer.camera,
|
||||
tl->rl_texture,
|
||||
{ 0, 1, -1.4f },
|
||||
1.0f);
|
||||
DrawBillboardNoShear(
|
||||
m_renderer.camera, tl->rl_texture, { 0, 1, -1.4f }, 1.0f);
|
||||
}
|
||||
|
||||
for(int h = 0; h < 2; ++h) {
|
||||
auto &handInfo = m_xr.hands.at(h);
|
||||
for(size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
|
||||
const auto &jl = handInfo.joint_locations[k]; // NOLINT
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
auto &handInfo = m_xr.hands.at(h);
|
||||
for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
|
||||
auto const &jl = handInfo.joint_locations[k]; // NOLINT
|
||||
Vector3 const pos {
|
||||
jl.pose.position.x,
|
||||
jl.pose.position.y,
|
||||
@@ -1783,7 +1813,7 @@ void LunarWM::render_3d(float /*dt*/)
|
||||
DrawSphere(pos, jl.radius, RED);
|
||||
}
|
||||
|
||||
DrawModel(m_renderer.hands.at(h), { 0, 0, 0 }, 1.0f, WHITE);
|
||||
// DrawModel(m_renderer.hands.at(h), { 0, 0, 0 }, 1.0f, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
module;
|
||||
|
||||
export module LunarWM.Math;
|
||||
|
||||
import std;
|
||||
|
||||
652
src/dhos_config.cpp
Normal file
652
src/dhos_config.cpp
Normal file
@@ -0,0 +1,652 @@
|
||||
#include "dhos_config.h"
|
||||
|
||||
// NOLINTBEGIN
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <print>
|
||||
#include <sstream>
|
||||
|
||||
namespace dhos {
|
||||
|
||||
// environment shared across parser and evaluation
|
||||
struct Environment {
|
||||
Environment(Environment *p = nullptr)
|
||||
: parent { p }
|
||||
{
|
||||
}
|
||||
std::unordered_map<std::string, Value> tbl;
|
||||
Environment *parent;
|
||||
bool contains(std::string const &k) const
|
||||
{
|
||||
return tbl.contains(k) || (parent && parent->contains(k));
|
||||
}
|
||||
Value &operator[](std::string const &k)
|
||||
{
|
||||
if (tbl.contains(k))
|
||||
return tbl.at(k);
|
||||
if (parent)
|
||||
return (*parent)[k];
|
||||
throw std::runtime_error(std::format("Undefined variable: {}", k));
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// default lib
|
||||
Object make_default_lib();
|
||||
Object merge_lib(Object const &base, Object const *extra)
|
||||
{
|
||||
Object out = base;
|
||||
if (extra)
|
||||
out.insert(extra->begin(), extra->end());
|
||||
return out;
|
||||
}
|
||||
|
||||
// env, lexer, parser
|
||||
|
||||
enum class Tok {
|
||||
End,
|
||||
Id,
|
||||
Num,
|
||||
Str,
|
||||
Path,
|
||||
LPar,
|
||||
RPar,
|
||||
LBr,
|
||||
RBr,
|
||||
LBrk,
|
||||
RBrk,
|
||||
Eq,
|
||||
Dot,
|
||||
Comma
|
||||
};
|
||||
|
||||
std::string Tok_str(Tok t)
|
||||
{
|
||||
if (t == Tok::End)
|
||||
return "End";
|
||||
else if (t == Tok::Id)
|
||||
return "Id";
|
||||
else if (t == Tok::Num)
|
||||
return "Num";
|
||||
else if (t == Tok::Str)
|
||||
return "Str";
|
||||
else if (t == Tok::Path)
|
||||
return "Path";
|
||||
else if (t == Tok::LPar)
|
||||
return "LPar";
|
||||
else if (t == Tok::RPar)
|
||||
return "RPar";
|
||||
else if (t == Tok::LBr)
|
||||
return "LBr";
|
||||
else if (t == Tok::RBr)
|
||||
return "RBr";
|
||||
else if (t == Tok::LBrk)
|
||||
return "LBrk";
|
||||
else if (t == Tok::RBrk)
|
||||
return "RBrk";
|
||||
else if (t == Tok::Eq)
|
||||
return "Eq";
|
||||
else if (t == Tok::Dot)
|
||||
return "Dot";
|
||||
else if (t == Tok::Comma)
|
||||
return "Comma";
|
||||
else
|
||||
return "?";
|
||||
}
|
||||
|
||||
struct T {
|
||||
Tok k;
|
||||
std::string t;
|
||||
};
|
||||
|
||||
class Lex {
|
||||
public:
|
||||
explicit Lex(std::string_view s)
|
||||
: src { s }
|
||||
{
|
||||
}
|
||||
T next();
|
||||
|
||||
private:
|
||||
T id();
|
||||
T num();
|
||||
T str();
|
||||
T path();
|
||||
void skip();
|
||||
std::string_view src;
|
||||
std::size_t pos {};
|
||||
};
|
||||
|
||||
T Lex::id()
|
||||
{
|
||||
std::size_t s = pos;
|
||||
while (pos < src.size()
|
||||
&& (std::isalnum((unsigned char)src[pos]) || src[pos] == '_'))
|
||||
++pos;
|
||||
return { Tok::Id, std::string { src.substr(s, pos - s) } };
|
||||
}
|
||||
T Lex::num()
|
||||
{
|
||||
std::size_t s = pos;
|
||||
while (pos < src.size()
|
||||
&& (std::isdigit((unsigned char)src[pos]) || src[pos] == '_'))
|
||||
++pos;
|
||||
if (pos < src.size() && src[pos] == '.') {
|
||||
++pos;
|
||||
while (pos < src.size()
|
||||
&& (std::isdigit((unsigned char)src[pos]) || src[pos] == '_'))
|
||||
++pos;
|
||||
}
|
||||
return { Tok::Num, std::string { src.substr(s, pos - s) } };
|
||||
}
|
||||
T Lex::str()
|
||||
{
|
||||
char q = src[pos++];
|
||||
std::string o;
|
||||
while (pos < src.size() && src[pos] != q)
|
||||
o.push_back(src[pos++]);
|
||||
if (pos >= src.size())
|
||||
throw std::runtime_error("Unterminated string literal");
|
||||
++pos;
|
||||
return { Tok::Str, o };
|
||||
}
|
||||
|
||||
static bool is_path_ch(char c)
|
||||
{
|
||||
return std::isalnum((unsigned char)c) || c == '_' || c == '.' || c == '/'
|
||||
|| c == '-' || c == ':';
|
||||
}
|
||||
|
||||
T Lex::path()
|
||||
{
|
||||
std::string out;
|
||||
while (pos < src.size()) {
|
||||
char c = src[pos];
|
||||
if (c == '\\') {
|
||||
++pos;
|
||||
if (pos < src.size()) {
|
||||
out.push_back(src[pos]);
|
||||
++pos;
|
||||
} else {
|
||||
out.push_back('\\');
|
||||
}
|
||||
} else if (is_path_ch(c)) {
|
||||
out.push_back(c);
|
||||
++pos;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { Tok::Path, out };
|
||||
}
|
||||
|
||||
void Lex::skip()
|
||||
{
|
||||
while (pos < src.size()) {
|
||||
if (std::isspace((unsigned char)src[pos])) {
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
if (src[pos] == '#') {
|
||||
while (pos < src.size() && src[pos] != '\n')
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
T Lex::next()
|
||||
{
|
||||
skip();
|
||||
if (pos >= src.size())
|
||||
return { Tok::End, "" };
|
||||
char c = src[pos];
|
||||
|
||||
if (c == '/' // "/foo"
|
||||
|| (c == '.' && pos + 1 < src.size() && src[pos + 1] == '/') // "./foo"
|
||||
|| (c == '.' && pos + 2 < src.size() && src[pos + 1] == '.'
|
||||
&& src[pos + 2] == '/') // "../foo"
|
||||
|| (std::isalpha((unsigned char)c) && pos + 1 < src.size()
|
||||
&& src[pos + 1] == ':')) // "C:/foo"
|
||||
return path();
|
||||
|
||||
switch (c) {
|
||||
case '(':
|
||||
++pos;
|
||||
return { Tok::LPar, "(" };
|
||||
case ')':
|
||||
++pos;
|
||||
return { Tok::RPar, ")" };
|
||||
case '{':
|
||||
++pos;
|
||||
return { Tok::LBr, "{" };
|
||||
case '}':
|
||||
++pos;
|
||||
return { Tok::RBr, "}" };
|
||||
case '[':
|
||||
++pos;
|
||||
return { Tok::LBrk, "[" };
|
||||
case ']':
|
||||
++pos;
|
||||
return { Tok::RBrk, "]" };
|
||||
case '=':
|
||||
++pos;
|
||||
return { Tok::Eq, "=" };
|
||||
case '.':
|
||||
++pos;
|
||||
return { Tok::Dot, "." };
|
||||
case ',':
|
||||
++pos;
|
||||
return { Tok::Comma, "," };
|
||||
case '"':
|
||||
case '\'':
|
||||
return str();
|
||||
case 0:
|
||||
return { Tok::End, "" };
|
||||
default:
|
||||
if (std::isdigit((unsigned char)c))
|
||||
return num();
|
||||
if (std::isalpha((unsigned char)c) || c == '_')
|
||||
return id();
|
||||
std::println("Invalid character: {} ({:d})", c, c);
|
||||
throw std::runtime_error(std::format("Invalid character: {}", c));
|
||||
}
|
||||
}
|
||||
|
||||
// parser
|
||||
class Parser {
|
||||
public:
|
||||
Parser(std::string_view s, std::shared_ptr<Environment> e)
|
||||
: lx { s }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
adv();
|
||||
}
|
||||
Value parse() { return val(); }
|
||||
|
||||
private:
|
||||
Lex lx;
|
||||
T cur;
|
||||
std::shared_ptr<Environment> env;
|
||||
void adv() { cur = lx.next(); }
|
||||
bool acc(Tok k)
|
||||
{
|
||||
if (cur.k == k) {
|
||||
adv();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void exp(Tok k)
|
||||
{
|
||||
if (!acc(k)) {
|
||||
std::println("Expected: {}, Got: {}", Tok_str(k), Tok_str(cur.k));
|
||||
throw std::runtime_error(
|
||||
std::format("Syntax error: expected {}, got {}", Tok_str(k),
|
||||
Tok_str(cur.k)));
|
||||
}
|
||||
}
|
||||
bool begins(Tok k)
|
||||
{
|
||||
return k == Tok::Path || k == Tok::Id || k == Tok::Num || k == Tok::Str
|
||||
|| k == Tok::LBr || k == Tok::LBrk || k == Tok::LPar;
|
||||
}
|
||||
std::string id()
|
||||
{
|
||||
std::string s = cur.t;
|
||||
exp(Tok::Id);
|
||||
return s;
|
||||
}
|
||||
|
||||
Value array()
|
||||
{
|
||||
exp(Tok::LBrk);
|
||||
Array a;
|
||||
while (cur.k != Tok::RBrk) {
|
||||
a.push_back(val());
|
||||
acc(Tok::Comma);
|
||||
}
|
||||
exp(Tok::RBrk);
|
||||
return Value { std::move(a), env };
|
||||
}
|
||||
Value block()
|
||||
{
|
||||
exp(Tok::LBr);
|
||||
Object o;
|
||||
while (cur.k != Tok::RBr) {
|
||||
if (acc(Tok::LPar)) {
|
||||
Value inner = val();
|
||||
exp(Tok::RPar);
|
||||
o.insert(inner.obj().begin(), inner.obj().end());
|
||||
continue;
|
||||
}
|
||||
std::string k = id();
|
||||
exp(Tok::Eq);
|
||||
o[k] = val();
|
||||
}
|
||||
exp(Tok::RBr);
|
||||
return Value { std::move(o), env };
|
||||
}
|
||||
Value func()
|
||||
{
|
||||
exp(Tok::Id);
|
||||
std::vector<std::string> params;
|
||||
while (cur.k == Tok::Id)
|
||||
params.push_back(id());
|
||||
exp(Tok::Eq);
|
||||
Value body = val();
|
||||
auto f = std::make_shared<Value::Function>();
|
||||
f->params = params;
|
||||
f->body = std::make_shared<Value>(body);
|
||||
return Value { std::move(f), env };
|
||||
}
|
||||
Value call()
|
||||
{
|
||||
std::string name = id();
|
||||
std::vector<Value> args;
|
||||
while (begins(cur.k))
|
||||
args.emplace_back(val());
|
||||
auto c = std::make_shared<Value::Call>();
|
||||
c->name = name;
|
||||
c->args = std::move(args);
|
||||
return Value { std::move(c), env };
|
||||
}
|
||||
Value val()
|
||||
{
|
||||
switch (cur.k) {
|
||||
case Tok::Id:
|
||||
if (cur.t == "fn") {
|
||||
return func();
|
||||
}
|
||||
if (cur.t == "nil") {
|
||||
adv();
|
||||
return Value { env };
|
||||
}
|
||||
return call();
|
||||
case Tok::Num: {
|
||||
std::string s = cur.t;
|
||||
s.erase(std::remove(s.begin(), s.end(), '_'), s.end());
|
||||
double n;
|
||||
if (static_cast<int>(
|
||||
std::from_chars(s.data(), s.data() + s.size(), n).ec))
|
||||
throw;
|
||||
adv();
|
||||
return Value { n, env };
|
||||
}
|
||||
case Tok::Str: {
|
||||
Value v { cur.t, env };
|
||||
adv();
|
||||
return v;
|
||||
}
|
||||
case Tok::Path: {
|
||||
Value v { std::filesystem::path { cur.t }, env };
|
||||
adv();
|
||||
return v;
|
||||
}
|
||||
case Tok::LBrk:
|
||||
return array();
|
||||
case Tok::LBr:
|
||||
return block();
|
||||
case Tok::LPar:
|
||||
adv();
|
||||
{
|
||||
Value v = val();
|
||||
exp(Tok::RPar);
|
||||
return v;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
std::format("Unexpected token: {}", Tok_str(cur.k)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// built-ins
|
||||
Value join(Array const &a)
|
||||
{
|
||||
Array r;
|
||||
for (auto &v : a) {
|
||||
if (!v.is_arr())
|
||||
throw;
|
||||
r.insert(r.end(), v.arr().begin(), v.arr().end());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
Value list_rng(Array const &a)
|
||||
{
|
||||
if (a.size() != 3 || !a[0].is_num() || !a[1].is_num() || !a[2].is_num())
|
||||
throw;
|
||||
double s = a[0].num(), e = a[1].num(), st = a[2].num();
|
||||
if (!st)
|
||||
throw;
|
||||
Array r;
|
||||
if (st > 0)
|
||||
for (double x = s; x <= e; x += st)
|
||||
r.emplace_back(x);
|
||||
else
|
||||
for (double x = s; x >= e; x += st)
|
||||
r.emplace_back(x);
|
||||
return r;
|
||||
}
|
||||
Value imp(Array const &a)
|
||||
{
|
||||
if (a.empty())
|
||||
throw;
|
||||
std::ifstream f;
|
||||
if (a[0].is_str()) {
|
||||
f = std::ifstream { a[0].str() };
|
||||
} else if (a[0].is_path()) {
|
||||
f = std::ifstream { a[0].path() };
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
if (!f)
|
||||
throw;
|
||||
std::stringstream b;
|
||||
b << f.rdbuf();
|
||||
std::istringstream in { b.str() };
|
||||
return parse_config(in);
|
||||
}
|
||||
Object make_default_lib()
|
||||
{
|
||||
Object l;
|
||||
l["join"] = Builtin { join };
|
||||
l["list_from_range"] = Builtin { list_rng };
|
||||
l["import"] = Builtin { imp };
|
||||
return l;
|
||||
}
|
||||
|
||||
// serializer
|
||||
void w(std::ostream &o, Value const &v)
|
||||
{
|
||||
if (v.is_nil()) {
|
||||
o << "nil";
|
||||
return;
|
||||
}
|
||||
if (v.is_num()) {
|
||||
o << v.num();
|
||||
return;
|
||||
}
|
||||
if (v.is_str()) {
|
||||
o << '"' << v.str() << '"';
|
||||
return;
|
||||
}
|
||||
if (v.is_path()) {
|
||||
o << v.path().generic_string();
|
||||
return;
|
||||
}
|
||||
if (v.is_arr()) {
|
||||
o << "[";
|
||||
bool f = true;
|
||||
for (auto &x : v.arr()) {
|
||||
if (!f)
|
||||
o << ",";
|
||||
w(o, x);
|
||||
f = false;
|
||||
}
|
||||
o << "]";
|
||||
return;
|
||||
}
|
||||
if (v.is_obj()) {
|
||||
o << "{";
|
||||
bool f = true;
|
||||
for (auto &[k, x] : v.obj()) {
|
||||
if (!f)
|
||||
o << ",";
|
||||
o << k << "=";
|
||||
w(o, x);
|
||||
f = false;
|
||||
}
|
||||
o << "}";
|
||||
return;
|
||||
}
|
||||
o << "fn"; // prints literal token for functions
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// public helpers
|
||||
Value eval(Value const &v, Object const *extra_lib)
|
||||
{
|
||||
if (v.is_call()) {
|
||||
auto const &c = v.call();
|
||||
if (!v.env)
|
||||
throw std::runtime_error(
|
||||
std::format("Function call with no environment: {}", c.name));
|
||||
Value cal = (*v.env)[c.name];
|
||||
Array args;
|
||||
for (auto &a : c.args)
|
||||
args.push_back(eval(a, extra_lib));
|
||||
if (auto b = std::get_if<Builtin>(&cal.data))
|
||||
return (*b)(args);
|
||||
if (auto fp
|
||||
= std::get_if<std::shared_ptr<Value::Function>>(&cal.data)) {
|
||||
Object lib;
|
||||
return *(*fp)->body;
|
||||
}
|
||||
return cal;
|
||||
}
|
||||
|
||||
if (!v.is_fn())
|
||||
return v; // already concrete
|
||||
|
||||
static Object base = make_default_lib();
|
||||
Object lib = merge_lib(base, extra_lib);
|
||||
|
||||
if (auto fp = std::get_if<std::shared_ptr<Value::Function>>(&v.data))
|
||||
return *(*fp)->body;
|
||||
|
||||
return v; // Builtin with no args
|
||||
}
|
||||
|
||||
// auto-evaluating operator[] impl
|
||||
static void materialise(Value &v)
|
||||
{
|
||||
auto r = eval(v, nullptr);
|
||||
if (!r.is_fn() && !r.is_call())
|
||||
v = std::move(r);
|
||||
}
|
||||
|
||||
Value &Value::operator[](std::string const &k)
|
||||
{
|
||||
materialise(*this);
|
||||
if (is_nil())
|
||||
return *this;
|
||||
if (!is_obj())
|
||||
throw std::logic_error("[] on non-object");
|
||||
|
||||
Value &child = obj()[k];
|
||||
materialise(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
Value &Value::operator[](std::size_t i)
|
||||
{
|
||||
materialise(*this);
|
||||
if (is_nil())
|
||||
return *this;
|
||||
if (!is_arr())
|
||||
throw std::logic_error("[] on non-array");
|
||||
if (i >= arr().size())
|
||||
throw std::out_of_range("index");
|
||||
|
||||
Value &child = arr()[i];
|
||||
materialise(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
Value const &Value::operator[](std::string const &k) const
|
||||
{
|
||||
return const_cast<Value *>(this)->operator[](k);
|
||||
}
|
||||
Value const &Value::operator[](std::size_t i) const
|
||||
{
|
||||
return const_cast<Value *>(this)->operator[](i);
|
||||
}
|
||||
|
||||
// parse/write
|
||||
Value parse_config(std::istream &in)
|
||||
{
|
||||
std::stringstream b;
|
||||
b << in.rdbuf();
|
||||
std::string src = b.str();
|
||||
auto env = std::make_shared<Environment>();
|
||||
env->tbl = make_default_lib();
|
||||
Parser p { src, env };
|
||||
return p.parse();
|
||||
}
|
||||
inline void resolve_paths(Value &v, std::filesystem::path const &base)
|
||||
{
|
||||
if (v.is_path()) {
|
||||
auto &p = v.path();
|
||||
if (p.is_relative())
|
||||
p = base / p;
|
||||
return;
|
||||
}
|
||||
if (v.is_arr()) {
|
||||
for (auto &x : v.arr())
|
||||
resolve_paths(x, base);
|
||||
return;
|
||||
}
|
||||
if (v.is_obj()) {
|
||||
for (auto &[_, x] : v.obj())
|
||||
resolve_paths(x, base);
|
||||
return;
|
||||
}
|
||||
if (v.is_call()) {
|
||||
for (auto &x : v.call().args)
|
||||
resolve_paths(x, base);
|
||||
}
|
||||
if (std::holds_alternative<std::shared_ptr<Value::Function>>(v.data)) {
|
||||
resolve_paths(
|
||||
*std::get<std::shared_ptr<Value::Function>>(v.data)->body, base);
|
||||
}
|
||||
}
|
||||
|
||||
Value parse_config(std::filesystem::path const &f)
|
||||
{
|
||||
std::ifstream in { f };
|
||||
if (!in)
|
||||
throw std::runtime_error(
|
||||
std::format("Failed to open configuration file: {}", f.string()));
|
||||
Value v = parse_config(in);
|
||||
resolve_paths(v, f.parent_path());
|
||||
return v;
|
||||
}
|
||||
void write_config(Value const &v, std::filesystem::path const &o)
|
||||
{
|
||||
std::ofstream f { o };
|
||||
if (!f)
|
||||
throw std::runtime_error(
|
||||
std::format("Failed to write configuration file: {}", o.string()));
|
||||
w(f, v);
|
||||
}
|
||||
|
||||
} // namespace dhos
|
||||
|
||||
// NOLINTEND
|
||||
143
src/dhos_config.h
Normal file
143
src/dhos_config.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
// NOLINTBEGIN
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <istream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace dhos {
|
||||
|
||||
struct Environment;
|
||||
struct Value;
|
||||
|
||||
using Array = std::vector<Value>;
|
||||
using Object = std::unordered_map<std::string, Value>;
|
||||
using Builtin = std::function<Value(Array const &)>;
|
||||
|
||||
struct Value {
|
||||
using EnvPtr = std::shared_ptr<Environment>;
|
||||
|
||||
struct Function {
|
||||
std::vector<std::string> params;
|
||||
std::shared_ptr<Value> body;
|
||||
};
|
||||
struct Call {
|
||||
std::string name;
|
||||
Array args;
|
||||
};
|
||||
using Data = std::variant<std::monostate, double, std::string,
|
||||
std::filesystem::path, Array, Object, std::shared_ptr<Function>,
|
||||
Builtin, std::shared_ptr<Call>>;
|
||||
Data data {};
|
||||
EnvPtr env {};
|
||||
|
||||
Value() = default;
|
||||
explicit Value(EnvPtr e)
|
||||
: env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(double n, EnvPtr e = {})
|
||||
: data { n }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(std::string const &s, EnvPtr e = {})
|
||||
: data { s }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(char const *s, EnvPtr e = {})
|
||||
: data { std::string { s } }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(std::filesystem::path const &p, EnvPtr e = {})
|
||||
: data { p }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(Array a, EnvPtr e = {})
|
||||
: data { std::move(a) }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(Object o, EnvPtr e = {})
|
||||
: data { std::move(o) }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(std::shared_ptr<Function> f, EnvPtr e = {})
|
||||
: data { std::move(f) }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(Builtin b, EnvPtr e = {})
|
||||
: data { std::move(b) }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
Value(std::shared_ptr<Call> c, EnvPtr e = {})
|
||||
: data { std::move(c) }
|
||||
, env { std::move(e) }
|
||||
{
|
||||
}
|
||||
|
||||
bool is_nil() const { return std::holds_alternative<std::monostate>(data); }
|
||||
bool is_num() const { return std::holds_alternative<double>(data); }
|
||||
bool is_str() const { return std::holds_alternative<std::string>(data); }
|
||||
bool is_path() const
|
||||
{
|
||||
return std::holds_alternative<std::filesystem::path>(data);
|
||||
}
|
||||
bool is_arr() const { return std::holds_alternative<Array>(data); }
|
||||
bool is_obj() const { return std::holds_alternative<Object>(data); }
|
||||
bool is_fn() const
|
||||
{
|
||||
return std::holds_alternative<std::shared_ptr<Function>>(data)
|
||||
|| std::holds_alternative<Builtin>(data);
|
||||
}
|
||||
bool is_call() const
|
||||
{
|
||||
return std::holds_alternative<std::shared_ptr<Call>>(data);
|
||||
}
|
||||
|
||||
double num() const { return std::get<double>(data); }
|
||||
std::string const &str() const { return std::get<std::string>(data); }
|
||||
Array &arr() { return std::get<Array>(data); }
|
||||
Array const &arr() const { return std::get<Array>(data); }
|
||||
Object &obj() { return std::get<Object>(data); }
|
||||
Object const &obj() const { return std::get<Object>(data); }
|
||||
std::filesystem::path &path()
|
||||
{
|
||||
return std::get<std::filesystem::path>(data);
|
||||
}
|
||||
std::filesystem::path const &path() const
|
||||
{
|
||||
return std::get<std::filesystem::path>(data);
|
||||
}
|
||||
Call &call() { return *std::get<std::shared_ptr<Call>>(data); }
|
||||
Call const &call() const { return *std::get<std::shared_ptr<Call>>(data); }
|
||||
|
||||
// auto-evaluating nlohmann-style access
|
||||
Value &operator[](std::string const &k);
|
||||
Value &operator[](std::size_t i);
|
||||
Value const &operator[](std::string const &k) const;
|
||||
Value const &operator[](std::size_t i) const;
|
||||
};
|
||||
|
||||
// pure evaluation helper (never mutates its argument)
|
||||
Value eval(Value const &v, Object const *extra_lib = nullptr);
|
||||
|
||||
Value parse_config(std::istream &in);
|
||||
Value parse_config(std::filesystem::path const &f);
|
||||
void write_config(Value const &v, std::filesystem::path const &out);
|
||||
|
||||
} // namespace dhos
|
||||
|
||||
// NOLINTEND
|
||||
Reference in New Issue
Block a user