Skip to content

Commit bc73244

Browse files
committed
refactor: javascript/lua parity
1 parent 9325d98 commit bc73244

File tree

4 files changed

+242
-4
lines changed

4 files changed

+242
-4
lines changed

include/mrdocs/Support/Lua.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ enum class Type
348348
};
349349

350350
/** A Lua value.
351+
352+
Table values map to DOM arrays when they contain only positive
353+
integer keys without gaps (Lua's 1-based indices are normalized to
354+
zero-based arrays). Otherwise tables map to DOM objects with
355+
stringified keys.
351356
*/
352357
class MRDOCS_DECL
353358
Value
@@ -423,6 +428,33 @@ class MRDOCS_DECL
423428
*/
424429
bool isTable() const noexcept;
425430

431+
/** Return the underlying boolean value.
432+
433+
@note Behavior is undefined if `!isBoolean()`
434+
*/
435+
bool getBool() const noexcept;
436+
437+
/** Return the underlying integer value (Lua numbers are coerced).
438+
439+
@note Behavior is undefined if `!isNumber()`
440+
*/
441+
std::int64_t getInteger() const noexcept;
442+
443+
/** Return the underlying string view.
444+
445+
@note Behavior is undefined if `!isString()`
446+
*/
447+
std::string_view getString() const noexcept;
448+
449+
/** Convert the value to a DOM value.
450+
451+
Tables are converted to a DOM array when they contain only
452+
positive integer keys with no gaps (Lua 1-based indices are
453+
normalized to zero-based DOM arrays). Otherwise tables are
454+
converted to DOM objects with stringified keys.
455+
*/
456+
dom::Value getDom() const;
457+
426458
/** Return a string representation.
427459
428460
This function is used for diagnostics.

src/lib/Support/Lua.cpp

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
#include <lib/Support/LuaHandlebars.hpp>
1212
#include <mrdocs/Support/Lua.hpp>
1313
#include <mrdocs/Support/Path.hpp>
14+
#include <mrdocs/Support/Assert.hpp>
1415
#include <mrdocs/Support/Report.hpp>
1516
#include <llvm/Support/raw_ostream.h>
17+
extern "C" {
1618
#include <lua.h>
1719
#include <lualib.h>
18-
#include <format>
1920
#include <lauxlib.h>
21+
}
22+
#include <format>
2023
#include <print>
24+
#include <algorithm>
25+
#include <map>
2126

2227
namespace mrdocs {
2328
namespace lua {
@@ -34,6 +39,7 @@ static char gImplKey{};
3439
//------------------------------------------------
3540

3641
static void domObject_push_metatable(Access& A);
42+
static void domArray_push_metatable(Access& A);
3743
static void domValue_push(Access& A, dom::Value const&);
3844

3945
//------------------------------------------------
@@ -278,6 +284,19 @@ domArray_get(
278284
lua_touserdata(A, index));
279285
}
280286

287+
static void
288+
domArray_push(
289+
Access& A,
290+
dom::Array const& arr)
291+
{
292+
auto& arr_ = *static_cast<
293+
dom::Array*>(lua_newuserdatauv(
294+
A, sizeof(dom::Array), 0));
295+
domArray_push_metatable(A);
296+
lua_setmetatable(A, -2);
297+
std::construct_at(&arr_, arr);
298+
}
299+
281300
// Push the domObject metatable onto the stack
282301
[[maybe_unused]]
283302
static
@@ -591,8 +610,7 @@ domValue_push(
591610
case dom::Kind::String:
592611
return luaM_pushstring(A, value.getString());
593612
case dom::Kind::Array:
594-
MRDOCS_UNREACHABLE();
595-
//return domArray_push(A, value.getArray());
613+
return domArray_push(A, value.getArray());
596614
case dom::Kind::Object:
597615
return domObject_push(A, value.getObject());
598616
default:
@@ -939,6 +957,104 @@ type() const noexcept
939957
}
940958
}
941959

960+
bool
961+
Value::getBool() const noexcept
962+
{
963+
MRDOCS_ASSERT(isBoolean());
964+
Access A(*scope_);
965+
return lua_toboolean(A, index_);
966+
}
967+
968+
std::int64_t
969+
Value::getInteger() const noexcept
970+
{
971+
MRDOCS_ASSERT(isNumber());
972+
Access A(*scope_);
973+
return static_cast<std::int64_t>(lua_tonumber(A, index_));
974+
}
975+
976+
std::string_view
977+
Value::getString() const noexcept
978+
{
979+
MRDOCS_ASSERT(isString());
980+
Access A(*scope_);
981+
return luaM_getstring(A, index_);
982+
}
983+
984+
static dom::Value
985+
luaValue_toDom(Access& A, int idx)
986+
{
987+
switch (lua_type(A, idx))
988+
{
989+
case LUA_TNIL:
990+
return dom::Value(nullptr);
991+
case LUA_TBOOLEAN:
992+
return dom::Value(lua_toboolean(A, idx) != 0);
993+
case LUA_TNUMBER:
994+
return dom::Value(static_cast<std::int64_t>(lua_tonumber(A, idx)));
995+
case LUA_TSTRING:
996+
return dom::Value(luaM_getstring(A, idx));
997+
case LUA_TFUNCTION:
998+
return dom::Value(dom::Kind::Undefined); // no Lua->DOM function bridge yet
999+
case LUA_TTABLE:
1000+
{
1001+
// Decide between array-like and object-like
1002+
std::map<std::size_t, dom::Value> intItems;
1003+
dom::Object obj;
1004+
bool arrayLike = true;
1005+
std::size_t maxIndex = 0;
1006+
1007+
lua_pushnil(A); // first key
1008+
while (lua_next(A, idx) != 0)
1009+
{
1010+
if (lua_isinteger(A, -2))
1011+
{
1012+
auto k = static_cast<std::size_t>(lua_tointeger(A, -2));
1013+
maxIndex = std::max(maxIndex, k);
1014+
intItems[k] = luaValue_toDom(A, -1);
1015+
}
1016+
else
1017+
{
1018+
arrayLike = false;
1019+
obj.set(std::string(luaM_getstring(A, -2)), luaValue_toDom(A, -1));
1020+
}
1021+
lua_pop(A, 1); // pop value keep key
1022+
}
1023+
1024+
if (arrayLike && obj.empty())
1025+
{
1026+
dom::Array arr;
1027+
for (std::size_t i = 1; i <= maxIndex; ++i)
1028+
{
1029+
auto it = intItems.find(i);
1030+
if (it != intItems.end())
1031+
arr.push_back(it->second);
1032+
else
1033+
arr.push_back(dom::Value());
1034+
}
1035+
return dom::Value(std::move(arr));
1036+
}
1037+
1038+
for (auto const& [k, v] : intItems)
1039+
{
1040+
obj.set(std::to_string(k), v);
1041+
}
1042+
return dom::Value(std::move(obj));
1043+
}
1044+
default:
1045+
return dom::Value(dom::Kind::Undefined);
1046+
}
1047+
}
1048+
1049+
dom::Value
1050+
Value::getDom() const
1051+
{
1052+
if (!scope_)
1053+
return dom::Value();
1054+
Access A(*scope_);
1055+
return luaValue_toDom(A, index_);
1056+
}
1057+
9421058
std::string
9431059
Value::
9441060
displayString() const

src/lib/Support/LuaHandlebars.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
namespace mrdocs {
1818

19-
/** Add the Handlebars Lua instance as a global
19+
/** Add the Handlebars Lua instance as a global.
20+
21+
Lua integration does not currently expose a per-helper registration API
22+
like the JavaScript bridge; helpers are loaded via the embedded scripts
23+
provided by tryLoadHandlebars().
2024
*/
2125
MRDOCS_DECL
2226
Expected<void>

src/test/Support/Lua.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
// Copyright (c) 2025 Alan de Freitas ([email protected])
7+
//
8+
// Official repository: https://github.com/cppalliance/mrdocs
9+
//
10+
11+
#include <mrdocs/Support/Lua.hpp>
12+
#include <test_suite/test_suite.hpp>
13+
14+
namespace mrdocs {
15+
namespace lua {
16+
17+
struct LuaSupportTest
18+
{
19+
void test_primitives()
20+
{
21+
Context ctx;
22+
Scope scope(ctx);
23+
24+
auto fnBool = scope.loadChunk("return true").value();
25+
Value b = fnBool();
26+
BOOST_TEST(b.isBoolean());
27+
BOOST_TEST(b.getBool() == true);
28+
BOOST_TEST(b.getDom().getBool() == true);
29+
30+
auto fnInt = scope.loadChunk("return 42").value();
31+
Value i = fnInt();
32+
BOOST_TEST(i.isNumber());
33+
BOOST_TEST(i.getInteger() == 42);
34+
BOOST_TEST(i.getDom().getInteger() == 42);
35+
36+
auto fnStr = scope.loadChunk("return 'hello'").value();
37+
Value s = fnStr();
38+
BOOST_TEST(s.isString());
39+
BOOST_TEST(s.getString() == "hello");
40+
BOOST_TEST(s.getDom().getString() == "hello");
41+
}
42+
43+
void test_array_table()
44+
{
45+
Context ctx;
46+
Scope scope(ctx);
47+
48+
auto fnArr = scope.loadChunk("return {1, 2, 3}").value();
49+
Value arr = fnArr();
50+
BOOST_TEST(arr.isTable());
51+
auto dv = arr.getDom();
52+
BOOST_TEST(dv.isArray());
53+
auto da = dv.getArray();
54+
BOOST_TEST(da.size() == 3);
55+
BOOST_TEST(da.get(0).getInteger() == 1);
56+
BOOST_TEST(da.get(1).getInteger() == 2);
57+
BOOST_TEST(da.get(2).getInteger() == 3);
58+
}
59+
60+
void test_object_table()
61+
{
62+
Context ctx;
63+
Scope scope(ctx);
64+
65+
auto fnObj = scope.loadChunk("return { a = 1, b = 'x' }").value();
66+
Value obj = fnObj();
67+
BOOST_TEST(obj.isTable());
68+
auto dv = obj.getDom();
69+
BOOST_TEST(dv.isObject());
70+
auto dObj = dv.getObject();
71+
BOOST_TEST(dObj.get("a").getInteger() == 1);
72+
BOOST_TEST(dObj.get("b").getString() == "x");
73+
}
74+
75+
void run()
76+
{
77+
test_primitives();
78+
test_array_table();
79+
test_object_table();
80+
}
81+
};
82+
83+
TEST_SUITE(LuaSupportTest, "mrdocs.lua.support");
84+
85+
} // namespace lua
86+
} // namespace mrdocs

0 commit comments

Comments
 (0)