silverbullet/common/space_lua/stdlib/string.ts

217 lines
6.0 KiB
TypeScript
Raw Normal View History

2024-10-10 02:35:07 +08:00
import {
2024-10-11 21:34:27 +08:00
LuaBuiltinFunction,
2025-01-09 16:00:29 +08:00
luaCall,
2024-10-11 21:34:27 +08:00
LuaMultiRes,
LuaTable,
luaToString,
2024-10-10 02:35:07 +08:00
} from "$common/space_lua/runtime.ts";
2025-01-16 22:33:18 +08:00
function createLuaMatcher(pattern: string, global = false) {
const jsPattern = pattern
.replace(/%(.)/g, (_, char) => {
switch (char) {
case ".":
return "[.]";
case "%":
return "%";
case "d":
return "\\d";
case "D":
return "\\D";
case "s":
return "\\s";
case "S":
return "\\S";
case "w":
return "\\w";
case "a":
return "[A-Za-z]";
case "l":
return "[a-z]";
case "u":
return "[A-Z]";
case "p":
return "[\\p{P}]";
default:
return char;
}
});
const regex = new RegExp(jsPattern, global ? "g" : undefined);
return (s: string) => {
return regex.exec(s);
};
}
2024-10-10 02:35:07 +08:00
export const stringApi = new LuaTable({
2024-10-20 21:06:23 +08:00
byte: new LuaBuiltinFunction((_sf, s: string, i?: number, j?: number) => {
2024-10-11 21:34:27 +08:00
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);
}),
2024-10-20 21:06:23 +08:00
char: new LuaBuiltinFunction((_sf, ...args: number[]) => {
2024-10-11 21:34:27 +08:00
return String.fromCharCode(...args);
}),
find: new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
(_sf, s: string, pattern: string, init?: number, plain?: boolean) => {
2024-10-11 21:34:27 +08:00
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,
]);
},
),
2024-10-20 21:06:23 +08:00
gmatch: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
2025-01-16 22:33:18 +08:00
const matcher = createLuaMatcher(pattern, true);
2024-10-11 21:34:27 +08:00
return () => {
2025-01-16 22:33:18 +08:00
const result = matcher(s);
2024-10-11 21:34:27 +08:00
if (!result) {
return;
}
const captures = result.slice(1);
return new LuaMultiRes(captures.length > 0 ? captures : [result[0]]);
2024-10-11 21:34:27 +08:00
};
}),
gsub: new LuaBuiltinFunction(
2025-01-09 16:00:29 +08:00
async (
sf,
s: string,
pattern: string,
repl: any, // string or LuaFunction
n?: number,
) => {
2024-10-11 21:34:27 +08:00
n = n ?? Infinity;
2025-01-09 16:00:29 +08:00
// Convert Lua patterns to JavaScript regex
// This handles:
// - %.: Match literal dot
// - %%: Match literal %
// - %d: Match digit
// - %s: Match whitespace
// - %w: Match word character
const jsPattern = pattern
.replace(/%(.)/g, (_, char) => {
switch (char) {
case ".":
return "[.]"; // Match literal dot using character class
case "%":
return "%"; // Match literal %
case "d":
return "\\d"; // Match digit
case "s":
return "\\s"; // Match whitespace
case "w":
return "\\w"; // Match word character
default:
return char; // Match literal character
}
});
const regex = new RegExp(jsPattern, "g");
2024-10-11 21:34:27 +08:00
let result = s;
2025-01-09 16:00:29 +08:00
let count = 0;
// Collect all matches first to handle replacements properly
const positions: Array<[number, number, string, string[]]> = [];
2024-10-11 21:34:27 +08:00
let match: RegExpExecArray | null;
2025-01-09 16:00:29 +08:00
let lastIndex = 0;
while ((match = regex.exec(result)) !== null && count < n) {
if (match.index >= lastIndex) {
positions.push([
match.index,
match[0].length,
match[0],
match.slice(1),
]);
count++;
lastIndex = match.index + 1;
}
regex.lastIndex = match.index + 1;
}
// Process replacements in reverse order to maintain string indices
for (let i = positions.length - 1; i >= 0; i--) {
const [index, length, fullMatch, captures] = positions[i];
let replacement: any;
if (repl.call) {
const args = captures.length > 0 ? captures : [fullMatch];
replacement = await luaCall(repl, args, sf.astCtx!, sf);
replacement = (replacement === null || replacement === undefined)
? fullMatch
: replacement;
} else {
replacement = repl;
2024-10-10 02:35:07 +08:00
}
2025-01-09 16:00:29 +08:00
result = result.slice(0, index) +
replacement +
result.slice(index + length);
2024-10-11 21:34:27 +08:00
}
2025-01-09 16:00:29 +08:00
return new LuaMultiRes([result, count]);
2024-10-11 21:34:27 +08:00
},
),
2024-10-20 21:06:23 +08:00
len: new LuaBuiltinFunction((_sf, s: string) => {
2024-10-11 21:34:27 +08:00
return s.length;
}),
2024-10-20 21:06:23 +08:00
lower: new LuaBuiltinFunction((_sf, s: string) => {
2024-10-11 21:34:27 +08:00
return luaToString(s.toLowerCase());
}),
2024-10-20 21:06:23 +08:00
upper: new LuaBuiltinFunction((_sf, s: string) => {
2024-10-11 21:34:27 +08:00
return luaToString(s.toUpperCase());
}),
match: new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
(_sf, s: string, pattern: string, init?: number) => {
2024-10-11 21:34:27 +08:00
init = init ?? 1;
2025-01-16 22:33:18 +08:00
const result = createLuaMatcher(pattern)(s.slice(init - 1));
2024-10-11 21:34:27 +08:00
if (!result) {
return new LuaMultiRes([]);
}
2025-01-16 22:33:18 +08:00
const captures = result.slice(1);
return new LuaMultiRes(captures.length > 0 ? captures : [result[0]]);
2024-10-11 21:34:27 +08:00
},
),
2024-10-20 21:06:23 +08:00
rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => {
2024-10-11 21:34:27 +08:00
sep = sep ?? "";
return s.repeat(n) + sep;
}),
2024-10-20 21:06:23 +08:00
reverse: new LuaBuiltinFunction((_sf, s: string) => {
2024-10-11 21:34:27 +08:00
return s.split("").reverse().join("");
}),
2024-10-20 21:06:23 +08:00
sub: new LuaBuiltinFunction((_sf, s: string, i: number, j?: number) => {
2024-10-11 21:34:27 +08:00
j = j ?? s.length;
return s.slice(i - 1, j);
}),
2024-10-20 21:06:23 +08:00
split: new LuaBuiltinFunction((_sf, s: string, sep: string) => {
2024-10-11 21:34:27 +08:00
return s.split(sep);
}),
2025-01-15 03:26:47 +08:00
// Non-standard
startswith: new LuaBuiltinFunction((_sf, s: string, prefix: string) => {
return s.startsWith(prefix);
}),
endswith: new LuaBuiltinFunction((_sf, s: string, suffix: string) => {
return s.endsWith(suffix);
}),
2025-01-18 01:32:01 +08:00
trim: new LuaBuiltinFunction((_sf, s: string) => {
return s.trim();
}),
trim_start: new LuaBuiltinFunction((_sf, s: string) => {
return s.trimStart();
}),
trim_end: new LuaBuiltinFunction((_sf, s: string) => {
return s.trimEnd();
}),
2024-10-10 02:35:07 +08:00
});