More lua progress: JS Interop
parent
7731b28203
commit
3319c7f21c
|
@ -49,16 +49,16 @@ export class SpaceLuaEnvironment {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const sbApi = new LuaTable();
|
env.set(
|
||||||
sbApi.set(
|
"command",
|
||||||
"register_command",
|
|
||||||
new LuaBuiltinFunction(
|
new LuaBuiltinFunction(
|
||||||
(def: LuaTable) => {
|
(def: LuaTable) => {
|
||||||
if (def.get(1) === undefined) {
|
if (def.get(1) === undefined) {
|
||||||
throw new Error("Callback is required");
|
throw new Error("Callback is required");
|
||||||
}
|
}
|
||||||
|
console.log("Registering Lua command", def.get("name"));
|
||||||
scriptEnv.registerCommand(
|
scriptEnv.registerCommand(
|
||||||
luaValueToJS(def) as any,
|
def.toJSObject() as any,
|
||||||
async (...args: any[]) => {
|
async (...args: any[]) => {
|
||||||
try {
|
try {
|
||||||
return await def.get(1).call(...args.map(jsToLuaValue));
|
return await def.get(1).call(...args.map(jsToLuaValue));
|
||||||
|
@ -90,7 +90,6 @@ export class SpaceLuaEnvironment {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
env.set("silverbullet", sbApi);
|
|
||||||
for (const script of allScripts) {
|
for (const script of allScripts) {
|
||||||
try {
|
try {
|
||||||
const ast = parseLua(script.script, { ref: script.ref });
|
const ast = parseLua(script.script, { ref: script.ref });
|
||||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
||||||
LuaStatement,
|
LuaStatement,
|
||||||
} from "$common/space_lua/ast.ts";
|
} from "$common/space_lua/ast.ts";
|
||||||
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
||||||
|
import { luaCall, luaSet } from "$common/space_lua/runtime.ts";
|
||||||
import {
|
import {
|
||||||
type ILuaFunction,
|
type ILuaFunction,
|
||||||
type ILuaGettable,
|
type ILuaGettable,
|
||||||
|
@ -209,36 +210,13 @@ function evalPrefixExpression(
|
||||||
return values.then(([table, key]) => {
|
return values.then(([table, key]) => {
|
||||||
table = singleResult(table);
|
table = singleResult(table);
|
||||||
key = singleResult(key);
|
key = singleResult(key);
|
||||||
if (!table) {
|
|
||||||
throw new LuaRuntimeError(
|
return luaGet(table, key, e.ctx);
|
||||||
`Attempting to index a nil value`,
|
|
||||||
e.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (key === null || key === undefined) {
|
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Attempting to index with a nil key`,
|
|
||||||
e.key.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return luaGet(table, key);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const table = singleResult(values[0]);
|
const table = singleResult(values[0]);
|
||||||
const key = singleResult(values[1]);
|
const key = singleResult(values[1]);
|
||||||
if (!table) {
|
return luaGet(table, singleResult(key), e.ctx);
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Attempting to index a nil value`,
|
|
||||||
e.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (key === null || key === undefined) {
|
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Attempting to index with a nil key`,
|
|
||||||
e.key.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return luaGet(table, singleResult(key));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// <expr>.property
|
// <expr>.property
|
||||||
|
@ -246,22 +224,10 @@ function evalPrefixExpression(
|
||||||
const obj = evalPrefixExpression(e.object, env);
|
const obj = evalPrefixExpression(e.object, env);
|
||||||
if (obj instanceof Promise) {
|
if (obj instanceof Promise) {
|
||||||
return obj.then((obj) => {
|
return obj.then((obj) => {
|
||||||
if (!obj?.get) {
|
return luaGet(obj, e.property, e.ctx);
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Attempting to index a nil value`,
|
|
||||||
e.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return obj.get(e.property);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!obj?.get) {
|
return luaGet(obj, e.property, e.ctx);
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Attempting to index a nil value`,
|
|
||||||
e.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return obj.get(e.property);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "FunctionCall": {
|
case "FunctionCall": {
|
||||||
|
@ -295,16 +261,18 @@ function evalPrefixExpression(
|
||||||
if (!prefixValue.call) {
|
if (!prefixValue.call) {
|
||||||
throw new LuaRuntimeError(
|
throw new LuaRuntimeError(
|
||||||
`Attempting to call ${prefixValue} as a function`,
|
`Attempting to call ${prefixValue} as a function`,
|
||||||
prefixValue.ctx,
|
e.prefix.ctx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const args = evalPromiseValues(
|
const args = evalPromiseValues(
|
||||||
e.args.map((arg) => evalExpression(arg, env)),
|
e.args.map((arg) => evalExpression(arg, env)),
|
||||||
);
|
);
|
||||||
if (args instanceof Promise) {
|
if (args instanceof Promise) {
|
||||||
return args.then((args) => prefixValue.call(...selfArgs, ...args));
|
return args.then((args) =>
|
||||||
|
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return prefixValue.call(...selfArgs, ...args);
|
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -330,9 +298,11 @@ function evalPrefixExpression(
|
||||||
e.args.map((arg) => evalExpression(arg, env)),
|
e.args.map((arg) => evalExpression(arg, env)),
|
||||||
);
|
);
|
||||||
if (args instanceof Promise) {
|
if (args instanceof Promise) {
|
||||||
return args.then((args) => prefixValue.call(...selfArgs, ...args));
|
return args.then((args) =>
|
||||||
|
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return prefixValue.call(...selfArgs, ...args);
|
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -466,7 +436,7 @@ export async function evalStatement(
|
||||||
.map((lval) => evalLValue(lval, env)));
|
.map((lval) => evalLValue(lval, env)));
|
||||||
|
|
||||||
for (let i = 0; i < lvalues.length; i++) {
|
for (let i = 0; i < lvalues.length; i++) {
|
||||||
lvalues[i].env.set(lvalues[i].key, values[i]);
|
luaSet(lvalues[i].env, lvalues[i].key, values[i], s.ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -691,24 +661,12 @@ function evalLValue(
|
||||||
);
|
);
|
||||||
if (objValue instanceof Promise) {
|
if (objValue instanceof Promise) {
|
||||||
return objValue.then((objValue) => {
|
return objValue.then((objValue) => {
|
||||||
if (!objValue.set) {
|
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Not a settable object: ${objValue}`,
|
|
||||||
lval.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
env: objValue,
|
env: objValue,
|
||||||
key: lval.property,
|
key: lval.property,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!objValue.set) {
|
|
||||||
throw new LuaRuntimeError(
|
|
||||||
`Not a settable object: ${objValue}`,
|
|
||||||
lval.object.ctx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
env: objValue,
|
env: objValue,
|
||||||
key: lval.property,
|
key: lval.property,
|
||||||
|
|
|
@ -21,7 +21,7 @@ Deno.test("Lua language tests", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function toPrettyString(err: LuaRuntimeError, code: string): string {
|
function toPrettyString(err: LuaRuntimeError, code: string): string {
|
||||||
if (!err.context.from || !err.context.to) {
|
if (!err.context || !err.context.from || !err.context.to) {
|
||||||
return err.toString();
|
return err.toString();
|
||||||
}
|
}
|
||||||
const from = err.context.from;
|
const from = err.context.from;
|
||||||
|
|
|
@ -16,8 +16,10 @@ local a = 1
|
||||||
local b = 2
|
local b = 2
|
||||||
assert(a + b == 3)
|
assert(a + b == 3)
|
||||||
|
|
||||||
-- Basic string concatenation
|
-- Basic string stuff
|
||||||
assert("Hello " .. "world" == "Hello world")
|
assert("Hello " .. "world" == "Hello world")
|
||||||
|
assert_equal([[Hello world]], "Hello world")
|
||||||
|
assert_equal([==[Hello [[world]]!]==], "Hello [[world]]!")
|
||||||
|
|
||||||
-- Various forms of function definitions
|
-- Various forms of function definitions
|
||||||
function f1()
|
function f1()
|
||||||
|
@ -303,3 +305,7 @@ table.sort(data, function(a, b)
|
||||||
end)
|
end)
|
||||||
assert_equal(data[1].name, "Jane")
|
assert_equal(data[1].name, "Jane")
|
||||||
assert_equal(data[2].name, "John")
|
assert_equal(data[2].name, "John")
|
||||||
|
|
||||||
|
-- os functions
|
||||||
|
assert(os.time() > 0)
|
||||||
|
assert(os.date("%Y-%m-%d", os.time({ year = 2020, month = 1, day = 1 })) == "2020-01-01")
|
||||||
|
|
|
@ -168,11 +168,16 @@ TableConstructor { "{" (field (fieldsep field)* fieldsep?)? "}" }
|
||||||
|
|
||||||
// Any sequence of characters except two consecutive ]]
|
// Any sequence of characters except two consecutive ]]
|
||||||
longStringContent { (![\]] | $[\]] ![\]])* }
|
longStringContent { (![\]] | $[\]] ![\]])* }
|
||||||
|
|
||||||
|
longDelimStringContent {
|
||||||
|
(![\]] | $[\]] ![=]+ ![\]])*
|
||||||
|
}
|
||||||
|
|
||||||
simpleString {
|
simpleString {
|
||||||
"'" (stringEscape | ![\r\n\\'])* "'" |
|
"'" (stringEscape | ![\r\n\\'])* "'" |
|
||||||
'"' (stringEscape | ![\r\n\\"])* '"' |
|
'"' (stringEscape | ![\r\n\\"])* '"' |
|
||||||
'[[' longStringContent ']]'
|
'[[' longStringContent ']]' |
|
||||||
|
'[' '='+ '[' longDelimStringContent ']' '='+ ']'
|
||||||
}
|
}
|
||||||
|
|
||||||
hex { $[0-9a-fA-F] }
|
hex { $[0-9a-fA-F] }
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const parser = LRParser.deserialize({
|
||||||
],
|
],
|
||||||
skippedNodes: [0,1],
|
skippedNodes: [0,1],
|
||||||
repeatNodeCount: 9,
|
repeatNodeCount: 9,
|
||||||
tokenData: "7U~RuXY#fYZ$Q[]#f]^$_pq#fqr$grs$rst)Yuv)_vw)dwx)ixy-zyz.Pz{.U{|.Z|}.`}!O.g!O!P/Z!P!Q/p!Q!R0Q!R![1f![!]3j!]!^3w!^!_4O!_!`4b!`!a4j!c!}4|!}#O5_#O#P#w#P#Q6c#Q#R6h#R#S4|#T#o4|#o#p6m#p#q6r#q#r6w#r#s6|~#kS#V~XY#f[]#fpq#f#O#P#w~#zQYZ#f]^#f~$VP#U~]^$Y~$_O#U~~$dP#U~YZ$YT$jP!_!`$mT$rOzT~$uXOY$rZ]$r^r$rrs%bs#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~%gO#Z~~%jZrs$rwx$r!Q![&]#O#P$r#T#U$r#U#V$r#Y#Z$r#b#c$r#i#j'}#l#m(p#n#o$r~&`ZOY$rZ]$r^r$rrs%bs!Q$r!Q!['R![#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~'UZOY$rZ]$r^r$rrs%bs!Q$r!Q![$r![#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~'zP;=`<%l$r~(QP#o#p(T~(WR!Q![(a!c!i(a#T#Z(a~(dS!Q![(a!c!i(a#T#Z(a#q#r$r~(sR!Q![(|!c!i(|#T#Z(|~)PR!Q![$r!c!i$r#T#Z$r~)_O#p~~)dO#m~~)iO#e~~)lXOY)iZ])i^w)iwx%bx#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~*[Zrs)iwx)i!Q![*}#O#P)i#T#U)i#U#V)i#Y#Z)i#b#c)i#i#j,o#l#m-b#n#o)i~+QZOY)iZ])i^w)iwx%bx!Q)i!Q![+s![#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~+vZOY)iZ])i^w)iwx%bx!Q)i!Q![)i![#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~,lP;=`<%l)i~,rP#o#p,u~,xR!Q![-R!c!i-R#T#Z-R~-US!Q![-R!c!i-R#T#Z-R#q#r)i~-eR!Q![-n!c!i-n#T#Z-n~-qR!Q![)i!c!i)i#T#Z)i~.POl~~.UOm~~.ZO#k~~.`O#i~V.gOvR#aS~.lP#j~}!O.o~.tTP~OY.oZ].o^;'S.o;'S;=`/T<%lO.o~/WP;=`<%l.oV/`PgT!O!P/cV/hP!PT!O!P/kQ/pOcQ~/uQ#l~!P!Q/{!_!`$m~0QO#n~~0VUd~!O!P0i!Q![1f!g!h0}!z!{1w#X#Y0}#l#m1w~0lP!Q![0o~0tRd~!Q![0o!g!h0}#X#Y0}~1QQ{|1W}!O1W~1ZP!Q![1^~1cPd~!Q![1^~1kSd~!O!P0i!Q![1f!g!h0}#X#Y0}~1zR!Q![2T!c!i2T#T#Z2T~2YUd~!O!P2l!Q![2T!c!i2T!r!s3^#T#Z2T#d#e3^~2oR!Q![2x!c!i2x#T#Z2x~2}Td~!Q![2x!c!i2x!r!s3^#T#Z2x#d#e3^~3aR{|1W}!O1W!P!Q1W~3oPo~![!]3r~3wOU~V4OOSR#aSV4VQ#uQzT!^!_4]!_!`$mT4bO#gT~4gP#`~!_!`$mV4qQ#vQzT!_!`$m!`!a4wT4|O#hT~5RS#X~!Q![4|!c!}4|#R#S4|#T#o4|~5dPi~!}#O5g~5jTO#P5g#P#Q5y#Q;'S5g;'S;=`6]<%lO5g~5|TO#P5g#P#Q%b#Q;'S5g;'S;=`6]<%lO5g~6`P;=`<%l5g~6hOj~~6mO#o~~6rOq~~6wO#d~~6|Ou~~7RP#f~!_!`$m",
|
tokenData: ";]~RuXY#fYZ$Q[]#f]^$_pq#fqr$grs$rst)Yuv)_vw)dwx)ixy-zyz.Pz{.U{|.Z|}.`}!O.g!O!P/Z!P!Q/p!Q!R0Q!R![1f![!]3j!]!^3w!^!_4O!_!`4b!`!a4j!c!}4|!}#O5_#O#P#w#P#Q:j#Q#R:o#R#S4|#T#o4|#o#p:t#p#q:y#q#r;O#r#s;T~#kS#V~XY#f[]#fpq#f#O#P#w~#zQYZ#f]^#f~$VP#U~]^$Y~$_O#U~~$dP#U~YZ$YT$jP!_!`$mT$rOzT~$uXOY$rZ]$r^r$rrs%bs#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~%gO#Z~~%jZrs$rwx$r!Q![&]#O#P$r#T#U$r#U#V$r#Y#Z$r#b#c$r#i#j'}#l#m(p#n#o$r~&`ZOY$rZ]$r^r$rrs%bs!Q$r!Q!['R![#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~'UZOY$rZ]$r^r$rrs%bs!Q$r!Q![$r![#O$r#O#P%g#P;'S$r;'S;=`'w<%lO$r~'zP;=`<%l$r~(QP#o#p(T~(WR!Q![(a!c!i(a#T#Z(a~(dS!Q![(a!c!i(a#T#Z(a#q#r$r~(sR!Q![(|!c!i(|#T#Z(|~)PR!Q![$r!c!i$r#T#Z$r~)_O#p~~)dO#m~~)iO#e~~)lXOY)iZ])i^w)iwx%bx#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~*[Zrs)iwx)i!Q![*}#O#P)i#T#U)i#U#V)i#Y#Z)i#b#c)i#i#j,o#l#m-b#n#o)i~+QZOY)iZ])i^w)iwx%bx!Q)i!Q![+s![#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~+vZOY)iZ])i^w)iwx%bx!Q)i!Q![)i![#O)i#O#P*X#P;'S)i;'S;=`,i<%lO)i~,lP;=`<%l)i~,rP#o#p,u~,xR!Q![-R!c!i-R#T#Z-R~-US!Q![-R!c!i-R#T#Z-R#q#r)i~-eR!Q![-n!c!i-n#T#Z-n~-qR!Q![)i!c!i)i#T#Z)i~.POl~~.UOm~~.ZO#k~~.`O#i~V.gOvR#aS~.lP#j~}!O.o~.tTP~OY.oZ].o^;'S.o;'S;=`/T<%lO.o~/WP;=`<%l.oV/`PgT!O!P/cV/hP!PT!O!P/kQ/pOcQ~/uQ#l~!P!Q/{!_!`$m~0QO#n~~0VUd~!O!P0i!Q![1f!g!h0}!z!{1w#X#Y0}#l#m1w~0lP!Q![0o~0tRd~!Q![0o!g!h0}#X#Y0}~1QQ{|1W}!O1W~1ZP!Q![1^~1cPd~!Q![1^~1kSd~!O!P0i!Q![1f!g!h0}#X#Y0}~1zR!Q![2T!c!i2T#T#Z2T~2YUd~!O!P2l!Q![2T!c!i2T!r!s3^#T#Z2T#d#e3^~2oR!Q![2x!c!i2x#T#Z2x~2}Td~!Q![2x!c!i2x!r!s3^#T#Z2x#d#e3^~3aR{|1W}!O1W!P!Q1W~3oPo~![!]3r~3wOU~V4OOSR#aSV4VQ#uQzT!^!_4]!_!`$mT4bO#gT~4gP#`~!_!`$mV4qQ#vQzT!_!`$m!`!a4wT4|O#hT~5RS#X~!Q![4|!c!}4|#R#S4|#T#o4|~5dQi~!_!`5j!}#O9n~5mQ!_!`5j!}#O5s~5vTO#P5s#P#Q6V#Q;'S5s;'S;=`9b<%lO5s~6YTO!_6i!_!`9R!`;'S6i;'S;=`9[<%lO6i~6lVO!_7R!_!`5s!`#P7R#P#Q6i#Q;'S7R;'S;=`9h<%lO7R~7UVO!_7R!_!`5s!`#P7R#P#Q7k#Q;'S7R;'S;=`9h<%lO7R~7nVO!_7R!_!`8T!`#P7R#P#Q6i#Q;'S7R;'S;=`9h<%lO7R~8WVO!_5s!_!`8T!`#P5s#P#Q8m#Q;'S5s;'S;=`9b<%lO5s~8rT#Z~O!_6i!_!`9R!`;'S6i;'S;=`9[<%lO6i~9UQ!_!`9R#P#Q%b~9_P;=`<%l6i~9eP;=`<%l5s~9kP;=`<%l7R~9qTO#P9n#P#Q:Q#Q;'S9n;'S;=`:d<%lO9n~:TTO#P9n#P#Q%b#Q;'S9n;'S;=`:d<%lO9n~:gP;=`<%l9n~:oOj~~:tO#o~~:yOq~~;OO#d~~;TOu~~;YP#f~!_!`$m",
|
||||||
tokenizers: [0, 1, 2],
|
tokenizers: [0, 1, 2],
|
||||||
topRules: {"Chunk":[0,2]},
|
topRules: {"Chunk":[0,2]},
|
||||||
dynamicPrecedences: {"110":1},
|
dynamicPrecedences: {"110":1},
|
||||||
|
|
|
@ -12,6 +12,7 @@ Deno.test("Test Lua parser", () => {
|
||||||
`e(1, 1.2, -3.8, +4, #lst, true, false, nil, "string", "", "Hello there \x00", ...)`,
|
`e(1, 1.2, -3.8, +4, #lst, true, false, nil, "string", "", "Hello there \x00", ...)`,
|
||||||
);
|
);
|
||||||
parse(`e([[hel]lo]], "Grinny face\\u{1F600}")`);
|
parse(`e([[hel]lo]], "Grinny face\\u{1F600}")`);
|
||||||
|
parse(`e([=[Hello page [[index]] end scene]=], [[yo]])`);
|
||||||
|
|
||||||
parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
|
parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
|
||||||
|
|
||||||
|
|
|
@ -331,11 +331,15 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const delimiterRegex = /^(\[=*\[)([\s\S]*)(\]=*\])$/;
|
||||||
|
|
||||||
// In case of quoted strings, remove the quotes and unescape the string
|
// In case of quoted strings, remove the quotes and unescape the string
|
||||||
// In case of a [[ type ]] literal string, remove the brackets
|
// In case of a [[ type ]] literal string, remove the brackets
|
||||||
function parseString(s: string): string {
|
function parseString(s: string): string {
|
||||||
if (s.startsWith("[[") && s.endsWith("]]")) {
|
// Handle long strings with delimiters
|
||||||
return s.slice(2, -2);
|
const delimiterMatch = s.match(delimiterRegex);
|
||||||
|
if (delimiterMatch) {
|
||||||
|
return delimiterMatch[2];
|
||||||
}
|
}
|
||||||
return s.slice(1, -1).replace(
|
return s.slice(1, -1).replace(
|
||||||
/\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g,
|
/\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g,
|
||||||
|
|
|
@ -295,6 +295,14 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSObject(): Record<string, any> {
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
for (const key of this.keys()) {
|
||||||
|
result[key] = luaValueToJS(this.get(key));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
if (this.metatable?.has("__tostring")) {
|
if (this.metatable?.has("__tostring")) {
|
||||||
const metaValue = this.metatable.get("__tostring");
|
const metaValue = this.metatable.get("__tostring");
|
||||||
|
@ -330,17 +338,39 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
||||||
|
|
||||||
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
||||||
|
|
||||||
export function luaSet(obj: any, key: any, value: any) {
|
export function luaSet(obj: any, key: any, value: any, ctx: ASTCtx) {
|
||||||
if (obj instanceof LuaTable) {
|
if (!obj) {
|
||||||
|
throw new LuaRuntimeError(
|
||||||
|
`Not a settable object: nil`,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof LuaTable || obj instanceof LuaEnv) {
|
||||||
obj.set(key, value);
|
obj.set(key, value);
|
||||||
} else {
|
} else {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function luaGet(obj: any, key: any): any {
|
export function luaGet(obj: any, key: any, ctx: ASTCtx): any {
|
||||||
if (obj instanceof LuaTable) {
|
if (!obj) {
|
||||||
|
throw new LuaRuntimeError(
|
||||||
|
`Attempting to index a nil value`,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (key === null || key === undefined) {
|
||||||
|
throw new LuaRuntimeError(
|
||||||
|
`Attempting to index with a nil key`,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof LuaTable || obj instanceof LuaEnv) {
|
||||||
return obj.get(key);
|
return obj.get(key);
|
||||||
|
} else if (typeof key === "number") {
|
||||||
|
return obj[key - 1];
|
||||||
} else {
|
} else {
|
||||||
return obj[key];
|
return obj[key];
|
||||||
}
|
}
|
||||||
|
@ -356,6 +386,21 @@ export function luaLen(obj: any): number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function luaCall(fn: any, args: any[], ctx: ASTCtx): any {
|
||||||
|
if (!fn) {
|
||||||
|
throw new LuaRuntimeError(
|
||||||
|
`Attempting to call a nil value`,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
const jsArgs = args.map(luaValueToJS);
|
||||||
|
// Native JS function
|
||||||
|
return fn(...jsArgs);
|
||||||
|
}
|
||||||
|
return fn.call(...args);
|
||||||
|
}
|
||||||
|
|
||||||
export function luaTypeOf(val: any): LuaType {
|
export function luaTypeOf(val: any): LuaType {
|
||||||
if (val === null || val === undefined) {
|
if (val === null || val === undefined) {
|
||||||
return "nil";
|
return "nil";
|
||||||
|
|
|
@ -3,11 +3,15 @@ import {
|
||||||
LuaBuiltinFunction,
|
LuaBuiltinFunction,
|
||||||
LuaEnv,
|
LuaEnv,
|
||||||
LuaMultiRes,
|
LuaMultiRes,
|
||||||
LuaTable,
|
type LuaTable,
|
||||||
luaToString,
|
luaToString,
|
||||||
luaTypeOf,
|
luaTypeOf,
|
||||||
type LuaValue,
|
type LuaValue,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
import { stringApi } from "$common/space_lua/stdlib/string.ts";
|
||||||
|
import { tableApi } from "$common/space_lua/stdlib/table.ts";
|
||||||
|
import { osApi } from "$common/space_lua/stdlib/os.ts";
|
||||||
|
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
||||||
|
|
||||||
const printFunction = new LuaBuiltinFunction((...args) => {
|
const printFunction = new LuaBuiltinFunction((...args) => {
|
||||||
console.log("[Lua]", ...args.map(luaToString));
|
console.log("[Lua]", ...args.map(luaToString));
|
||||||
|
@ -108,151 +112,31 @@ const getmetatableFunction = new LuaBuiltinFunction((table: LuaTable) => {
|
||||||
return table.metatable;
|
return table.metatable;
|
||||||
});
|
});
|
||||||
|
|
||||||
const stringFunctions = new LuaTable({
|
|
||||||
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
|
|
||||||
i = i ?? 1;
|
|
||||||
j = j ?? i;
|
|
||||||
const result = [];
|
|
||||||
for (let k = i; k <= j; k++) {
|
|
||||||
result.push(s.charCodeAt(k - 1));
|
|
||||||
}
|
|
||||||
return new LuaMultiRes(result);
|
|
||||||
}),
|
|
||||||
char: new LuaBuiltinFunction((...args: number[]) => {
|
|
||||||
return String.fromCharCode(...args);
|
|
||||||
}),
|
|
||||||
find: new LuaBuiltinFunction(
|
|
||||||
(s: string, pattern: string, init?: number, plain?: boolean) => {
|
|
||||||
init = init ?? 1;
|
|
||||||
plain = plain ?? false;
|
|
||||||
const result = s.slice(init - 1).match(pattern);
|
|
||||||
if (!result) {
|
|
||||||
return new LuaMultiRes([]);
|
|
||||||
}
|
|
||||||
return new LuaMultiRes([
|
|
||||||
result.index! + 1,
|
|
||||||
result.index! + result[0].length,
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
format: new LuaBuiltinFunction((format: string, ...args: any[]) => {
|
|
||||||
return format.replace(/%./g, (match) => {
|
|
||||||
switch (match) {
|
|
||||||
case "%s":
|
|
||||||
return luaToString(args.shift());
|
|
||||||
case "%d":
|
|
||||||
return String(args.shift());
|
|
||||||
default:
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
gmatch: new LuaBuiltinFunction((s: string, pattern: string) => {
|
|
||||||
const regex = new RegExp(pattern, "g");
|
|
||||||
return () => {
|
|
||||||
const result = regex.exec(s);
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return new LuaMultiRes(result.slice(1));
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
gsub: new LuaBuiltinFunction(
|
|
||||||
(s: string, pattern: string, repl: string, n?: number) => {
|
|
||||||
n = n ?? Infinity;
|
|
||||||
const regex = new RegExp(pattern, "g");
|
|
||||||
let result = s;
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
match = regex.exec(result);
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result = result.replace(match[0], repl);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
len: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return s.length;
|
|
||||||
}),
|
|
||||||
lower: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return luaToString(s.toLowerCase());
|
|
||||||
}),
|
|
||||||
upper: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return luaToString(s.toUpperCase());
|
|
||||||
}),
|
|
||||||
match: new LuaBuiltinFunction(
|
|
||||||
(s: string, pattern: string, init?: number) => {
|
|
||||||
init = init ?? 1;
|
|
||||||
const result = s.slice(init - 1).match(pattern);
|
|
||||||
if (!result) {
|
|
||||||
return new LuaMultiRes([]);
|
|
||||||
}
|
|
||||||
return new LuaMultiRes(result.slice(1));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
rep: new LuaBuiltinFunction((s: string, n: number, sep?: string) => {
|
|
||||||
sep = sep ?? "";
|
|
||||||
return s.repeat(n) + sep;
|
|
||||||
}),
|
|
||||||
reverse: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return s.split("").reverse().join("");
|
|
||||||
}),
|
|
||||||
sub: new LuaBuiltinFunction((s: string, i: number, j?: number) => {
|
|
||||||
j = j ?? s.length;
|
|
||||||
return s.slice(i - 1, j);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tableFunctions = new LuaTable({
|
|
||||||
concat: new LuaBuiltinFunction(
|
|
||||||
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
|
||||||
sep = sep ?? "";
|
|
||||||
i = i ?? 1;
|
|
||||||
j = j ?? tbl.length;
|
|
||||||
const result = [];
|
|
||||||
for (let k = i; k <= j; k++) {
|
|
||||||
result.push(tbl.get(k));
|
|
||||||
}
|
|
||||||
return result.join(sep);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
insert: new LuaBuiltinFunction(
|
|
||||||
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
|
||||||
if (value === undefined) {
|
|
||||||
value = posOrValue;
|
|
||||||
posOrValue = tbl.length + 1;
|
|
||||||
}
|
|
||||||
tbl.insert(posOrValue, value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
|
|
||||||
pos = pos ?? tbl.length;
|
|
||||||
tbl.remove(pos);
|
|
||||||
}),
|
|
||||||
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
|
|
||||||
return tbl.sort(comp);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function luaBuildStandardEnv() {
|
export function luaBuildStandardEnv() {
|
||||||
const env = new LuaEnv();
|
const env = new LuaEnv();
|
||||||
|
// Top-level builtins
|
||||||
env.set("print", printFunction);
|
env.set("print", printFunction);
|
||||||
env.set("assert", assertFunction);
|
env.set("assert", assertFunction);
|
||||||
env.set("pairs", pairsFunction);
|
|
||||||
env.set("ipairs", ipairsFunction);
|
|
||||||
env.set("type", typeFunction);
|
env.set("type", typeFunction);
|
||||||
env.set("tostring", tostringFunction);
|
env.set("tostring", tostringFunction);
|
||||||
env.set("tonumber", tonumberFunction);
|
env.set("tonumber", tonumberFunction);
|
||||||
env.set("error", errorFunction);
|
|
||||||
env.set("pcall", pcallFunction);
|
|
||||||
env.set("xpcall", xpcallFunction);
|
|
||||||
env.set("unpack", unpackFunction);
|
env.set("unpack", unpackFunction);
|
||||||
|
// Iterators
|
||||||
|
env.set("pairs", pairsFunction);
|
||||||
|
env.set("ipairs", ipairsFunction);
|
||||||
|
// meta table stuff
|
||||||
env.set("setmetatable", setmetatableFunction);
|
env.set("setmetatable", setmetatableFunction);
|
||||||
env.set("getmetatable", getmetatableFunction);
|
env.set("getmetatable", getmetatableFunction);
|
||||||
env.set("rawset", rawsetFunction);
|
env.set("rawset", rawsetFunction);
|
||||||
env.set("string", stringFunctions);
|
// Error handling
|
||||||
env.set("table", tableFunctions);
|
env.set("error", errorFunction);
|
||||||
|
env.set("pcall", pcallFunction);
|
||||||
|
env.set("xpcall", xpcallFunction);
|
||||||
|
|
||||||
|
// APIs
|
||||||
|
env.set("string", stringApi);
|
||||||
|
env.set("table", tableApi);
|
||||||
|
env.set("os", osApi);
|
||||||
|
env.set("js", jsApi);
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
jsToLuaValue,
|
||||||
|
LuaBuiltinFunction,
|
||||||
|
LuaTable,
|
||||||
|
luaValueToJS,
|
||||||
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
|
export const jsApi = new LuaTable({
|
||||||
|
new: new LuaBuiltinFunction(
|
||||||
|
(constructorFn: any, ...args) => {
|
||||||
|
return new constructorFn(
|
||||||
|
...args.map(luaValueToJS),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
importModule: new LuaBuiltinFunction((url) => {
|
||||||
|
return import(url);
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Binds a function to an object, so that the function can be called with the object as `this`. Some JS APIs require this.
|
||||||
|
*/
|
||||||
|
bind: new LuaBuiltinFunction((fn: any, obj: any, ...args: any[]) => {
|
||||||
|
return fn.bind(obj, ...args);
|
||||||
|
}),
|
||||||
|
tolua: new LuaBuiltinFunction(jsToLuaValue),
|
||||||
|
tojs: new LuaBuiltinFunction(luaValueToJS),
|
||||||
|
log: new LuaBuiltinFunction((...args) => {
|
||||||
|
console.log(...args);
|
||||||
|
}),
|
||||||
|
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
||||||
|
// (globalThis as any)[name] = value;
|
||||||
|
// }),
|
||||||
|
});
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { LuaBuiltinFunction, LuaTable } from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
|
export const osApi = new LuaTable({
|
||||||
|
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
|
||||||
|
if (tbl) {
|
||||||
|
// Build a date object from the table
|
||||||
|
const date = new Date();
|
||||||
|
if (!tbl.has("year")) {
|
||||||
|
throw new Error("time(): year is required");
|
||||||
|
}
|
||||||
|
date.setFullYear(tbl.get("year"));
|
||||||
|
if (!tbl.has("month")) {
|
||||||
|
throw new Error("time(): month is required");
|
||||||
|
}
|
||||||
|
date.setMonth(tbl.get("month") - 1);
|
||||||
|
if (!tbl.has("day")) {
|
||||||
|
throw new Error("time(): day is required");
|
||||||
|
}
|
||||||
|
date.setDate(tbl.get("day"));
|
||||||
|
date.setHours(tbl.get("hour") ?? 12);
|
||||||
|
date.setMinutes(tbl.get("min") ?? 0);
|
||||||
|
date.setSeconds(tbl.get("sec") ?? 0);
|
||||||
|
return Math.floor(date.getTime() / 1000);
|
||||||
|
} else {
|
||||||
|
return Math.floor(Date.now() / 1000);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Returns a string or a table containing date and time, formatted according to the given string format.
|
||||||
|
* If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time.
|
||||||
|
* If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year, month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61, due to leap seconds), wday (weekday, 1–7, Sunday is 1), yday (day of the year, 1–366), and isdst (daylight saving flag, a boolean). This last field may be absent if the information is not available.
|
||||||
|
* If format is not "*t", then date returns the date as a string, formatted according to the same rules as the ISO C function strftime.
|
||||||
|
* If format is absent, it defaults to "%c", which gives a human-readable date and time representation using the current locale.
|
||||||
|
*/
|
||||||
|
date: new LuaBuiltinFunction((format: string, timestamp?: number) => {
|
||||||
|
const date = timestamp ? new Date(timestamp * 1000) : new Date();
|
||||||
|
|
||||||
|
// Default Lua-like format when no format string is provided
|
||||||
|
if (!format) {
|
||||||
|
return date.toDateString() + " " + date.toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define mappings for Lua-style placeholders
|
||||||
|
const formatMap: { [key: string]: () => string } = {
|
||||||
|
// Year
|
||||||
|
"%Y": () => date.getFullYear().toString(),
|
||||||
|
"%y": () => (date.getFullYear() % 100).toString().padStart(2, "0"),
|
||||||
|
// Month
|
||||||
|
"%m": () => (date.getMonth() + 1).toString().padStart(2, "0"),
|
||||||
|
"%b": () => date.toLocaleString("en-US", { month: "short" }),
|
||||||
|
"%B": () => date.toLocaleString("en-US", { month: "long" }),
|
||||||
|
// Day
|
||||||
|
"%d": () => date.getDate().toString().padStart(2, "0"),
|
||||||
|
"%e": () => date.getDate().toString(),
|
||||||
|
// Hour
|
||||||
|
"%H": () => date.getHours().toString().padStart(2, "0"),
|
||||||
|
"%I": () =>
|
||||||
|
(date.getHours() % 12 || 12).toString().padStart(2, "0"),
|
||||||
|
// Minute
|
||||||
|
"%M": () => date.getMinutes().toString().padStart(2, "0"),
|
||||||
|
// Second
|
||||||
|
"%S": () => date.getSeconds().toString().padStart(2, "0"),
|
||||||
|
// AM/PM
|
||||||
|
"%p": () => date.getHours() >= 12 ? "PM" : "AM",
|
||||||
|
// Day of the week
|
||||||
|
"%A": () => date.toLocaleString("en-US", { weekday: "long" }),
|
||||||
|
"%a": () => date.toLocaleString("en-US", { weekday: "short" }),
|
||||||
|
"%w": () => date.getDay().toString(),
|
||||||
|
// Day of the year
|
||||||
|
"%j": () => {
|
||||||
|
const start = new Date(date.getFullYear(), 0, 0);
|
||||||
|
const diff = date.getTime() - start.getTime();
|
||||||
|
const oneDay = 1000 * 60 * 60 * 24;
|
||||||
|
const dayOfYear = Math.floor(diff / oneDay);
|
||||||
|
return dayOfYear.toString().padStart(3, "0");
|
||||||
|
},
|
||||||
|
// Time zone
|
||||||
|
"%Z": () => {
|
||||||
|
const match = date.toTimeString().match(/\((.*)\)/);
|
||||||
|
return match ? match[1] : "";
|
||||||
|
},
|
||||||
|
"%z": () => {
|
||||||
|
const offset = -date.getTimezoneOffset();
|
||||||
|
const sign = offset >= 0 ? "+" : "-";
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60).toString().padStart(
|
||||||
|
2,
|
||||||
|
"0",
|
||||||
|
);
|
||||||
|
const minutes = (absOffset % 60).toString().padStart(2, "0");
|
||||||
|
return `${sign}${hours}${minutes}`;
|
||||||
|
},
|
||||||
|
// Literal %
|
||||||
|
"%%": () => "%",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace format placeholders with corresponding values
|
||||||
|
return format.replace(/%[A-Za-z%]/g, (match) => {
|
||||||
|
const formatter = formatMap[match];
|
||||||
|
return formatter ? formatter() : match;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,106 @@
|
||||||
|
import {
|
||||||
|
LuaBuiltinFunction,
|
||||||
|
LuaMultiRes,
|
||||||
|
LuaTable,
|
||||||
|
luaToString,
|
||||||
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
|
export const stringApi = new LuaTable({
|
||||||
|
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
|
||||||
|
i = i ?? 1;
|
||||||
|
j = j ?? i;
|
||||||
|
const result = [];
|
||||||
|
for (let k = i; k <= j; k++) {
|
||||||
|
result.push(s.charCodeAt(k - 1));
|
||||||
|
}
|
||||||
|
return new LuaMultiRes(result);
|
||||||
|
}),
|
||||||
|
char: new LuaBuiltinFunction((...args: number[]) => {
|
||||||
|
return String.fromCharCode(...args);
|
||||||
|
}),
|
||||||
|
find: new LuaBuiltinFunction(
|
||||||
|
(s: string, pattern: string, init?: number, plain?: boolean) => {
|
||||||
|
init = init ?? 1;
|
||||||
|
plain = plain ?? false;
|
||||||
|
const result = s.slice(init - 1).match(pattern);
|
||||||
|
if (!result) {
|
||||||
|
return new LuaMultiRes([]);
|
||||||
|
}
|
||||||
|
return new LuaMultiRes([
|
||||||
|
result.index! + 1,
|
||||||
|
result.index! + result[0].length,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
format: new LuaBuiltinFunction((format: string, ...args: any[]) => {
|
||||||
|
return format.replace(/%./g, (match) => {
|
||||||
|
switch (match) {
|
||||||
|
case "%s":
|
||||||
|
return luaToString(args.shift());
|
||||||
|
case "%d":
|
||||||
|
return String(args.shift());
|
||||||
|
default:
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
gmatch: new LuaBuiltinFunction((s: string, pattern: string) => {
|
||||||
|
const regex = new RegExp(pattern, "g");
|
||||||
|
return () => {
|
||||||
|
const result = regex.exec(s);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return new LuaMultiRes(result.slice(1));
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
gsub: new LuaBuiltinFunction(
|
||||||
|
(s: string, pattern: string, repl: string, n?: number) => {
|
||||||
|
n = n ?? Infinity;
|
||||||
|
const regex = new RegExp(pattern, "g");
|
||||||
|
let result = s;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
match = regex.exec(result);
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result = result.replace(match[0], repl);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
len: new LuaBuiltinFunction((s: string) => {
|
||||||
|
return s.length;
|
||||||
|
}),
|
||||||
|
lower: new LuaBuiltinFunction((s: string) => {
|
||||||
|
return luaToString(s.toLowerCase());
|
||||||
|
}),
|
||||||
|
upper: new LuaBuiltinFunction((s: string) => {
|
||||||
|
return luaToString(s.toUpperCase());
|
||||||
|
}),
|
||||||
|
match: new LuaBuiltinFunction(
|
||||||
|
(s: string, pattern: string, init?: number) => {
|
||||||
|
init = init ?? 1;
|
||||||
|
const result = s.slice(init - 1).match(pattern);
|
||||||
|
if (!result) {
|
||||||
|
return new LuaMultiRes([]);
|
||||||
|
}
|
||||||
|
return new LuaMultiRes(result.slice(1));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rep: new LuaBuiltinFunction((s: string, n: number, sep?: string) => {
|
||||||
|
sep = sep ?? "";
|
||||||
|
return s.repeat(n) + sep;
|
||||||
|
}),
|
||||||
|
reverse: new LuaBuiltinFunction((s: string) => {
|
||||||
|
return s.split("").reverse().join("");
|
||||||
|
}),
|
||||||
|
sub: new LuaBuiltinFunction((s: string, i: number, j?: number) => {
|
||||||
|
j = j ?? s.length;
|
||||||
|
return s.slice(i - 1, j);
|
||||||
|
}),
|
||||||
|
split: new LuaBuiltinFunction((s: string, sep: string) => {
|
||||||
|
return s.split(sep);
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
type ILuaFunction,
|
||||||
|
LuaBuiltinFunction,
|
||||||
|
LuaTable,
|
||||||
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
|
export const tableApi = new LuaTable({
|
||||||
|
concat: new LuaBuiltinFunction(
|
||||||
|
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
||||||
|
sep = sep ?? "";
|
||||||
|
i = i ?? 1;
|
||||||
|
j = j ?? tbl.length;
|
||||||
|
const result = [];
|
||||||
|
for (let k = i; k <= j; k++) {
|
||||||
|
result.push(tbl.get(k));
|
||||||
|
}
|
||||||
|
return result.join(sep);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
insert: new LuaBuiltinFunction(
|
||||||
|
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
||||||
|
if (value === undefined) {
|
||||||
|
value = posOrValue;
|
||||||
|
posOrValue = tbl.length + 1;
|
||||||
|
}
|
||||||
|
tbl.insert(posOrValue, value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
|
||||||
|
pos = pos ?? tbl.length;
|
||||||
|
tbl.remove(pos);
|
||||||
|
}),
|
||||||
|
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
|
||||||
|
return tbl.sort(comp);
|
||||||
|
}),
|
||||||
|
});
|
|
@ -21,6 +21,7 @@
|
||||||
args: [],
|
args: [],
|
||||||
build: {
|
build: {
|
||||||
arch: "x86_64",
|
arch: "x86_64",
|
||||||
|
os: "browser",
|
||||||
},
|
},
|
||||||
core: {
|
core: {
|
||||||
runMicrotasks() {
|
runMicrotasks() {
|
||||||
|
|
Loading…
Reference in New Issue