Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-07-23 15:57:12 +03:00
parent 78c86aaabd
commit 14f2e52722
7 changed files with 1000 additions and 66 deletions

File diff suppressed because one or more lines are too long

6
flake.lock generated
View File

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

View File

@@ -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);
}
}

View File

@@ -1,5 +1,3 @@
module;
export module LunarWM.Math;
import std;

652
src/dhos_config.cpp Normal file
View 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
View 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