@silverbulletmd/silverbullet 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -25
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +0 -5
- package/client/plugos/hooks/event.ts +0 -6
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +1 -7
- package/client/plugos/hooks/syscall.ts +24 -14
- package/client/plugos/manifest_cache.ts +0 -32
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/asset.ts +61 -23
- package/client/plugos/syscalls/clientStore.ts +32 -6
- package/client/plugos/syscalls/client_code_widget.ts +9 -4
- package/client/plugos/syscalls/code_widget.ts +26 -11
- package/client/plugos/syscalls/config.ts +146 -30
- package/client/plugos/syscalls/datastore.ts +146 -40
- package/client/plugos/syscalls/editor.ts +1518 -722
- package/client/plugos/syscalls/event.ts +44 -13
- package/client/plugos/syscalls/fetch.ts +120 -83
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +267 -121
- package/client/plugos/syscalls/jsonschema.ts +107 -9
- package/client/plugos/syscalls/language.ts +38 -12
- package/client/plugos/syscalls/markdown.ts +166 -50
- package/client/plugos/syscalls/mq.ts +92 -20
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/schema_introspection.ts +33 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/service_registry.ts +74 -20
- package/client/plugos/syscalls/shell.ts +51 -21
- package/client/plugos/syscalls/space.ts +412 -113
- package/client/plugos/syscalls/sync.ts +103 -20
- package/client/plugos/syscalls/system.ts +274 -139
- package/client/plugos/system.ts +28 -10
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/api_documentation.ts +107 -0
- package/client/space_lua/ast.ts +13 -5
- package/client/space_lua/ast_narrow.ts +0 -5
- package/client/space_lua/budget.ts +113 -0
- package/client/space_lua/budget_ui.ts +49 -0
- package/client/space_lua/eval.ts +125 -92
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +243 -125
- package/client/space_lua/pretty_print.ts +128 -54
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +7 -51
- package/client/space_lua/query_env.ts +0 -2
- package/client/space_lua/render_lua_markdown.ts +2 -7
- package/client/space_lua/render_widget.ts +173 -0
- package/client/space_lua/runtime.ts +133 -92
- package/client/space_lua/stdlib/crypto.ts +8 -3
- package/client/space_lua/stdlib/encoding.ts +27 -9
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +136 -23
- package/client/space_lua/stdlib/load.ts +24 -16
- package/client/space_lua/stdlib/math.ts +331 -133
- package/client/space_lua/stdlib/net.ts +57 -13
- package/client/space_lua/stdlib/os.ts +111 -54
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +344 -21
- package/client/space_lua/stdlib/string.ts +282 -104
- package/client/space_lua/stdlib/string_pack.ts +58 -22
- package/client/space_lua/stdlib/table.ts +195 -45
- package/client/space_lua/stdlib.ts +354 -125
- package/client/space_lua/syscalls.ts +270 -0
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +15 -5
- package/plug-api/lib/async.ts +1 -6
- package/plug-api/lib/collation.ts +22 -0
- package/plug-api/lib/crypto.ts +16 -15
- package/plug-api/lib/dates.ts +33 -0
- package/plug-api/lib/fuzzy.ts +282 -0
- package/plug-api/lib/json.ts +0 -6
- package/plug-api/lib/limited_map.ts +0 -2
- package/plug-api/lib/link_write.ts +27 -0
- package/plug-api/{ui → lib}/panel_styles.ts +4 -3
- package/plug-api/lib/ref.ts +124 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +17 -1
- package/plug-api/lib/tags.ts +0 -3
- package/plug-api/lib/transclusion.ts +28 -1
- package/plug-api/lib/tree.ts +0 -5
- package/plug-api/lib/yaml.ts +3 -42
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +7 -5
- package/plug-api/syscalls/editor.ts +70 -208
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +39 -5
- package/plug-api/syscalls/jsonschema.ts +9 -0
- package/plug-api/syscalls/lua.ts +7 -0
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/shell.ts +0 -1
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +53 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +15 -2
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +62 -1
- package/plug-api/types/manifest.ts +22 -4
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/cx.ts +1 -3
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +61 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/slugify.ts +44 -0
- package/plug-api/ui/tree_model.ts +263 -0
- package/plug-api/ui/tree_types.ts +66 -0
- package/plug-api/ui/use_fit_collapse.ts +67 -0
- package/plugs/builtin_plugs.ts +0 -1
- package/plugs/index/types.ts +9 -0
- package/client/plugos/syscalls/lua.ts +0 -81
- package/plug-api/lib/memory_cache.ts +0 -21
|
@@ -35,7 +35,6 @@ class LabelResolveError extends Error {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Cache
|
|
39
38
|
const functionMetaByRoot = new WeakMap<LuaBlock, FunctionMeta>();
|
|
40
39
|
const functionMetaByAnyBlock = new WeakMap<LuaBlock, FunctionMeta>();
|
|
41
40
|
|
|
@@ -107,11 +106,9 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
|
|
|
107
106
|
roleByBlock,
|
|
108
107
|
);
|
|
109
108
|
|
|
110
|
-
// Validate gotos
|
|
111
109
|
for (const g of vctx.gotos) {
|
|
112
110
|
const target = g.node.name;
|
|
113
111
|
|
|
114
|
-
// Search current block for the label, then ancestors
|
|
115
112
|
let searchBlock: LuaBlock | undefined = g.block;
|
|
116
113
|
let labelIndex: number | undefined;
|
|
117
114
|
let labelDefBlock: LuaBlock | undefined;
|
|
@@ -151,7 +148,6 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
|
|
|
151
148
|
);
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
// Local scope forward jump check
|
|
155
151
|
let entersLocalScope = false;
|
|
156
152
|
let entersCloseScope = false;
|
|
157
153
|
|
|
@@ -7,7 +7,6 @@ export interface LuaTaggedFloat {
|
|
|
7
7
|
readonly isFloat: true;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
// Pre-allocated singletons for float zeros
|
|
11
10
|
const FLOAT_POS_ZERO: LuaTaggedFloat = { value: 0, isFloat: true };
|
|
12
11
|
const FLOAT_NEG_ZERO: LuaTaggedFloat = { value: -0, isFloat: true };
|
|
13
12
|
|
|
@@ -159,7 +158,6 @@ export function coerceNumericPair(
|
|
|
159
158
|
): CoerceNumericPairResult {
|
|
160
159
|
const forceFloat = op === "/" || op === "^";
|
|
161
160
|
|
|
162
|
-
// Both plain numbers
|
|
163
161
|
if (typeof a === "number" && typeof b === "number") {
|
|
164
162
|
const lt = leftType ?? inferNumericType(a);
|
|
165
163
|
const rt = rightType ?? inferNumericType(b);
|
|
@@ -175,7 +173,6 @@ export function coerceNumericPair(
|
|
|
175
173
|
};
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
// One tagged float, one plain number
|
|
179
176
|
if (typeof a === "number" && isTaggedFloat(b)) {
|
|
180
177
|
return {
|
|
181
178
|
left: a,
|
|
@@ -192,7 +189,6 @@ export function coerceNumericPair(
|
|
|
192
189
|
};
|
|
193
190
|
}
|
|
194
191
|
|
|
195
|
-
// Both tagged floats
|
|
196
192
|
if (isTaggedFloat(a) && isTaggedFloat(b)) {
|
|
197
193
|
return {
|
|
198
194
|
left: a.value,
|
|
@@ -201,7 +197,6 @@ export function coerceNumericPair(
|
|
|
201
197
|
};
|
|
202
198
|
}
|
|
203
199
|
|
|
204
|
-
// General fallback
|
|
205
200
|
const A = coerceNumeric(a, leftType);
|
|
206
201
|
const B = coerceNumeric(b, rightType);
|
|
207
202
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { lezerToParseTree } from "../../client/markdown_parser/parse_tree.ts";
|
|
2
2
|
import type { SyntaxNode } from "@lezer/common";
|
|
3
3
|
import {
|
|
4
|
-
cleanTree,
|
|
5
4
|
type ParseTree,
|
|
5
|
+
renderToText,
|
|
6
6
|
} from "@silverbulletmd/silverbullet/lib/tree";
|
|
7
7
|
// @ts-expect-error - Local generated JavaScript file without type definitions
|
|
8
8
|
import { parser } from "./parse-lua.js";
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ASTCtx,
|
|
13
13
|
LuaAttName,
|
|
14
14
|
LuaBlock,
|
|
15
|
+
LuaComment,
|
|
15
16
|
LuaExpression,
|
|
16
17
|
LuaFunctionBody,
|
|
17
18
|
LuaFunctionCallExpression,
|
|
@@ -25,6 +26,7 @@ import type {
|
|
|
25
26
|
LuaStatement,
|
|
26
27
|
LuaTableField,
|
|
27
28
|
} from "./ast.ts";
|
|
29
|
+
import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
|
|
28
30
|
import { LuaAttribute } from "./ast.ts";
|
|
29
31
|
import { getBlockGotoMeta } from "./labels.ts";
|
|
30
32
|
import { LuaRuntimeError, LuaStackFrame } from "./runtime.ts";
|
|
@@ -48,7 +50,6 @@ const customIndent = indentNodeProp.add({
|
|
|
48
50
|
},
|
|
49
51
|
});
|
|
50
52
|
|
|
51
|
-
// Use the customIndent in your language support
|
|
52
53
|
export const luaLanguage = LRLanguage.define({
|
|
53
54
|
name: "space-lua",
|
|
54
55
|
parser: parser.configure({
|
|
@@ -67,7 +68,217 @@ function parseChunk(t: ParseTree, ctx: ASTCtx): LuaBlock {
|
|
|
67
68
|
if (t.type !== "Chunk") {
|
|
68
69
|
throw new Error(`Expected Chunk, got ${t.type}`);
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
const block = t.children?.find((child) => child.type === "Block");
|
|
72
|
+
if (!block) {
|
|
73
|
+
return { type: "Block", statements: [], ctx: context(t, ctx) };
|
|
74
|
+
}
|
|
75
|
+
return parseBlockNode(block, ctx);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function collectLuaComments(t: ParseTree, ctx: ASTCtx): LuaComment[] {
|
|
79
|
+
const comments: LuaComment[] = [];
|
|
80
|
+
const visit = (node: ParseTree) => {
|
|
81
|
+
if (node.type === "Comment") {
|
|
82
|
+
const text = renderToText(node);
|
|
83
|
+
comments.push({
|
|
84
|
+
type: "Comment",
|
|
85
|
+
text,
|
|
86
|
+
kind: /^--\[(=*)\[/.test(text) ? "long" : "line",
|
|
87
|
+
ctx: context(node, ctx),
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const child of node.children ?? []) visit(child);
|
|
92
|
+
};
|
|
93
|
+
visit(t);
|
|
94
|
+
return comments.sort((a, b) => (a.ctx.from ?? 0) - (b.ctx.from ?? 0));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function withoutComments(t: ParseTree): ParseTree {
|
|
98
|
+
if (!t.children) return t;
|
|
99
|
+
return {
|
|
100
|
+
...t,
|
|
101
|
+
children: t.children
|
|
102
|
+
.filter((child) => child.type !== "Comment")
|
|
103
|
+
.map(withoutComments),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function assignCommentsToBlocks(
|
|
108
|
+
root: LuaBlock,
|
|
109
|
+
comments: LuaComment[],
|
|
110
|
+
source: string,
|
|
111
|
+
): void {
|
|
112
|
+
const blocks: LuaBlock[] = [];
|
|
113
|
+
const seen = new WeakSet<object>();
|
|
114
|
+
const visit = (value: unknown, key?: string) => {
|
|
115
|
+
if (!value || typeof value !== "object" || key === "ctx") return;
|
|
116
|
+
if (seen.has(value)) return;
|
|
117
|
+
seen.add(value);
|
|
118
|
+
if ((value as { type?: string }).type === "Block") {
|
|
119
|
+
blocks.push(value as LuaBlock);
|
|
120
|
+
}
|
|
121
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
122
|
+
if (
|
|
123
|
+
childKey !== "ctx" &&
|
|
124
|
+
childKey !== "comments" &&
|
|
125
|
+
childKey !== "documentation"
|
|
126
|
+
) {
|
|
127
|
+
visit(child, childKey);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
visit(root);
|
|
132
|
+
|
|
133
|
+
for (const comment of comments) {
|
|
134
|
+
const from = comment.ctx.from ?? 0;
|
|
135
|
+
const to = comment.ctx.to ?? from;
|
|
136
|
+
let owner = root;
|
|
137
|
+
let ownerSpan = Number.POSITIVE_INFINITY;
|
|
138
|
+
for (const block of blocks) {
|
|
139
|
+
if (block === root) continue;
|
|
140
|
+
const blockFrom = block.ctx.from;
|
|
141
|
+
const blockTo = block.ctx.to;
|
|
142
|
+
if (blockFrom === undefined || blockTo === undefined) continue;
|
|
143
|
+
const contains = blockFrom <= from && blockTo >= to;
|
|
144
|
+
const immediatelyBefore =
|
|
145
|
+
to <= blockFrom && !source.slice(to, blockFrom).trim();
|
|
146
|
+
const immediatelyAfter =
|
|
147
|
+
blockTo <= from && !source.slice(blockTo, from).trim();
|
|
148
|
+
if (
|
|
149
|
+
(contains || immediatelyBefore || immediatelyAfter) &&
|
|
150
|
+
blockTo - blockFrom < ownerSpan
|
|
151
|
+
) {
|
|
152
|
+
owner = block;
|
|
153
|
+
ownerSpan = blockTo - blockFrom;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
(owner.comments ??= []).push(comment);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseFunctionDocumentation(
|
|
161
|
+
comments: LuaComment[],
|
|
162
|
+
): LuaFunctionDocumentation | undefined {
|
|
163
|
+
const description: string[] = [];
|
|
164
|
+
const parameters: NonNullable<LuaFunctionDocumentation["parameters"]> = [];
|
|
165
|
+
const returns: NonNullable<LuaFunctionDocumentation["returns"]> = [];
|
|
166
|
+
let deprecated: string | boolean | undefined;
|
|
167
|
+
let see: string | undefined;
|
|
168
|
+
|
|
169
|
+
for (const comment of comments) {
|
|
170
|
+
const line = comment.text.slice(3).replace(/^\s?/, "");
|
|
171
|
+
const param = /^@param\s+(\S+)\s+(\S+)(?:\s+(.*))?$/.exec(line);
|
|
172
|
+
if (param) {
|
|
173
|
+
const optional = param[1].endsWith("?");
|
|
174
|
+
parameters.push({
|
|
175
|
+
name: optional ? param[1].slice(0, -1) : param[1],
|
|
176
|
+
type: param[2],
|
|
177
|
+
description: param[3] || undefined,
|
|
178
|
+
optional,
|
|
179
|
+
});
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const returnDoc = /^@return\s+(\S+)(?:\s+(.*))?$/.exec(line);
|
|
183
|
+
if (returnDoc) {
|
|
184
|
+
returns.push({
|
|
185
|
+
type: returnDoc[1],
|
|
186
|
+
description: returnDoc[2] || undefined,
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const deprecatedDoc = /^@deprecated(?:\s+(.*))?$/.exec(line);
|
|
191
|
+
if (deprecatedDoc) {
|
|
192
|
+
deprecated = deprecatedDoc[1] || true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const seeDoc = /^@see\s+(.+)$/.exec(line);
|
|
196
|
+
if (seeDoc) {
|
|
197
|
+
see = seeDoc[1];
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (!line.startsWith("@")) description.push(line);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const docs: LuaFunctionDocumentation = {};
|
|
204
|
+
const descriptionText = description.join("\n").trim();
|
|
205
|
+
if (descriptionText) docs.description = descriptionText;
|
|
206
|
+
if (parameters.length) docs.parameters = parameters;
|
|
207
|
+
if (returns.length) docs.returns = returns;
|
|
208
|
+
if (deprecated !== undefined) docs.deprecated = deprecated;
|
|
209
|
+
if (see) docs.see = see;
|
|
210
|
+
return Object.keys(docs).length ? docs : undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function attachFunctionDocumentation(
|
|
214
|
+
root: LuaBlock,
|
|
215
|
+
comments: LuaComment[],
|
|
216
|
+
source: string,
|
|
217
|
+
): void {
|
|
218
|
+
const candidates: { from: number; body: LuaFunctionBody }[] = [];
|
|
219
|
+
const seen = new WeakSet<object>();
|
|
220
|
+
const claimedBodies = new WeakSet<LuaFunctionBody>();
|
|
221
|
+
|
|
222
|
+
const addCandidate = (from: number | undefined, body: LuaFunctionBody) => {
|
|
223
|
+
if (from === undefined || claimedBodies.has(body)) return;
|
|
224
|
+
claimedBodies.add(body);
|
|
225
|
+
candidates.push({ from, body });
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const visit = (value: unknown, key?: string) => {
|
|
229
|
+
if (!value || typeof value !== "object" || key === "ctx") return;
|
|
230
|
+
if (seen.has(value)) return;
|
|
231
|
+
seen.add(value);
|
|
232
|
+
const node = value as Record<string, any>;
|
|
233
|
+
if (node.type === "Function" || node.type === "LocalFunction") {
|
|
234
|
+
addCandidate(node.ctx?.from, node.body);
|
|
235
|
+
} else if (
|
|
236
|
+
(node.type === "Local" || node.type === "Assignment") &&
|
|
237
|
+
node.expressions?.length === 1 &&
|
|
238
|
+
node.expressions[0]?.type === "FunctionDefinition"
|
|
239
|
+
) {
|
|
240
|
+
addCandidate(node.ctx?.from, node.expressions[0].body);
|
|
241
|
+
} else if (node.type === "FunctionDefinition") {
|
|
242
|
+
addCandidate(node.ctx?.from, node.body);
|
|
243
|
+
}
|
|
244
|
+
for (const [childKey, child] of Object.entries(node)) {
|
|
245
|
+
if (
|
|
246
|
+
childKey !== "ctx" &&
|
|
247
|
+
childKey !== "comments" &&
|
|
248
|
+
childKey !== "documentation"
|
|
249
|
+
) {
|
|
250
|
+
visit(child, childKey);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
visit(root);
|
|
255
|
+
|
|
256
|
+
const docComments = comments.filter(
|
|
257
|
+
(comment) => comment.kind === "line" && comment.text.startsWith("---"),
|
|
258
|
+
);
|
|
259
|
+
for (const candidate of candidates) {
|
|
260
|
+
const group: LuaComment[] = [];
|
|
261
|
+
let boundary = candidate.from;
|
|
262
|
+
for (let i = docComments.length - 1; i >= 0; i--) {
|
|
263
|
+
const comment = docComments[i];
|
|
264
|
+
const from = comment.ctx.from ?? 0;
|
|
265
|
+
const to = comment.ctx.to ?? from;
|
|
266
|
+
if (to > boundary) continue;
|
|
267
|
+
const gap = source.slice(to, boundary);
|
|
268
|
+
const lineStart = source.lastIndexOf("\n", from - 1) + 1;
|
|
269
|
+
if (
|
|
270
|
+
gap.trim() ||
|
|
271
|
+
/\r?\n[ \t]*\r?\n/.test(gap) ||
|
|
272
|
+
source.slice(lineStart, from).trim()
|
|
273
|
+
) {
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
group.unshift(comment);
|
|
277
|
+
boundary = from;
|
|
278
|
+
}
|
|
279
|
+
const documentation = parseFunctionDocumentation(group);
|
|
280
|
+
if (documentation) candidate.body.documentation = documentation;
|
|
281
|
+
}
|
|
71
282
|
}
|
|
72
283
|
|
|
73
284
|
function hasCloseLocal(names: LuaAttName[] | undefined): boolean {
|
|
@@ -335,7 +546,6 @@ function functionBodyCapturesNames(
|
|
|
335
546
|
return blockReferencesNames(body.block, check);
|
|
336
547
|
}
|
|
337
548
|
|
|
338
|
-
// Walk block using `exprReferencesNames` (inside a function body).
|
|
339
549
|
function blockReferencesNames(block: LuaBlock, names: Set<string>): boolean {
|
|
340
550
|
for (let i = 0; i < block.statements.length; i++) {
|
|
341
551
|
if (statementReferencesNames(block.statements[i], names)) return true;
|
|
@@ -438,7 +648,6 @@ function statementReferencesNames(
|
|
|
438
648
|
}
|
|
439
649
|
}
|
|
440
650
|
|
|
441
|
-
// Walk block looking for `FunctionDefinition` nodes that capture `names`.
|
|
442
651
|
function blockCapturesNames(block: LuaBlock, names: Set<string>): boolean {
|
|
443
652
|
for (let i = 0; i < block.statements.length; i++) {
|
|
444
653
|
if (statementCapturesNames(block.statements[i], names)) return true;
|
|
@@ -531,8 +740,6 @@ function statementCapturesNames(s: LuaStatement, names: Set<string>): boolean {
|
|
|
531
740
|
}
|
|
532
741
|
}
|
|
533
742
|
|
|
534
|
-
// At loop block level find `FunctionDefinition` and check if it
|
|
535
|
-
// captures `names`.
|
|
536
743
|
function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
|
|
537
744
|
if (!e) return false;
|
|
538
745
|
switch (e.type) {
|
|
@@ -659,7 +866,6 @@ function parseBlockNode(t: ParseTree, ctx: ASTCtx): LuaBlock {
|
|
|
659
866
|
case "Label": {
|
|
660
867
|
hasLabel = true;
|
|
661
868
|
hasLabelHere = true;
|
|
662
|
-
// Duplicate labels in the same block are illegal
|
|
663
869
|
const name = (s as any).name as string;
|
|
664
870
|
if (!dup) {
|
|
665
871
|
if (seen.has(name)) {
|
|
@@ -995,7 +1201,6 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
|
|
|
995
1201
|
case "break":
|
|
996
1202
|
return { type: "Break", ctx: context(t, ctx) };
|
|
997
1203
|
default:
|
|
998
|
-
// Gracefully ignore unknown empty nodes
|
|
999
1204
|
if (!t.children || t.children.length === 0) {
|
|
1000
1205
|
return {
|
|
1001
1206
|
type: "Semicolon",
|
|
@@ -1151,10 +1356,7 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
|
|
|
1151
1356
|
|
|
1152
1357
|
const delimiterRegex = /^(\[=*\[)([\s\S]*)(\]=*\])$/;
|
|
1153
1358
|
|
|
1154
|
-
// In case of quoted strings, remove the quotes and unescape the string
|
|
1155
|
-
// In case of a [[ type ]] literal string, remove the brackets
|
|
1156
1359
|
function parseString(s: string): string {
|
|
1157
|
-
// Handle long strings with delimiters
|
|
1158
1360
|
const delimiterMatch = s.match(delimiterRegex);
|
|
1159
1361
|
if (delimiterMatch) {
|
|
1160
1362
|
let text = delimiterMatch[2];
|
|
@@ -1191,11 +1393,9 @@ function parseString(s: string): string {
|
|
|
1191
1393
|
case "'":
|
|
1192
1394
|
return "'"; // Single quote
|
|
1193
1395
|
default:
|
|
1194
|
-
// Handle hexadecimal \x00
|
|
1195
1396
|
if (capture.startsWith("x")) {
|
|
1196
1397
|
return String.fromCharCode(parseInt(capture.slice(1), 16));
|
|
1197
1398
|
}
|
|
1198
|
-
// Handle unicode \u{XXXX}
|
|
1199
1399
|
if (capture.startsWith("u{")) {
|
|
1200
1400
|
const codePoint = parseInt(capture.slice(2, -1), 16);
|
|
1201
1401
|
return String.fromCodePoint(codePoint);
|
|
@@ -1223,7 +1423,6 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
|
|
|
1223
1423
|
const text = t.children![0].text!.toLowerCase();
|
|
1224
1424
|
return {
|
|
1225
1425
|
type: "Number",
|
|
1226
|
-
// Use the integer parser fox 0x literals
|
|
1227
1426
|
// biome-ignore lint/correctness/useParseIntRadix: hex strings need auto-detect radix
|
|
1228
1427
|
value: text.includes("x") ? parseInt(text) : parseFloat(text),
|
|
1229
1428
|
numericType: /[.eEpP]/.test(text) ? "float" : "int",
|
|
@@ -1269,7 +1468,11 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
|
|
|
1269
1468
|
};
|
|
1270
1469
|
|
|
1271
1470
|
case "Parens":
|
|
1272
|
-
return
|
|
1471
|
+
return {
|
|
1472
|
+
type: "Parenthesized",
|
|
1473
|
+
expression: parseExpression(t.children![1], ctx),
|
|
1474
|
+
ctx: context(t, ctx),
|
|
1475
|
+
};
|
|
1273
1476
|
case "FunctionCall": {
|
|
1274
1477
|
return parseFunctionCall(t, ctx);
|
|
1275
1478
|
}
|
|
@@ -1435,7 +1638,6 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
|
|
|
1435
1638
|
}
|
|
1436
1639
|
}
|
|
1437
1640
|
|
|
1438
|
-
// Parse a single OrderBy node (shared by query OrderByClause and AggOrderBy)
|
|
1439
1641
|
function parseOrderByNode(child: ParseTree, ctx: ASTCtx): LuaOrderBy {
|
|
1440
1642
|
const kids = child.children!;
|
|
1441
1643
|
let direction: "asc" | "desc" = "asc";
|
|
@@ -1469,7 +1671,6 @@ function parseOrderByNode(child: ParseTree, ctx: ASTCtx): LuaOrderBy {
|
|
|
1469
1671
|
return ob;
|
|
1470
1672
|
}
|
|
1471
1673
|
|
|
1472
|
-
// Parse an AggOrderBy node into LuaOrderBy[]
|
|
1473
1674
|
function parseAggOrderBy(t: ParseTree, ctx: ASTCtx): LuaOrderBy[] {
|
|
1474
1675
|
if (t.type !== "AggOrderBy") {
|
|
1475
1676
|
throw new Error(`Expected AggOrderBy, got ${t.type}`);
|
|
@@ -1483,7 +1684,6 @@ function parseAggOrderBy(t: ParseTree, ctx: ASTCtx): LuaOrderBy[] {
|
|
|
1483
1684
|
return orderBy;
|
|
1484
1685
|
}
|
|
1485
1686
|
|
|
1486
|
-
// Parse function args, extracting AggOrderBy if present inside funcParams
|
|
1487
1687
|
function parseFunctionArgsWithOrderBy(
|
|
1488
1688
|
ts: ParseTree[],
|
|
1489
1689
|
ctx: ASTCtx,
|
|
@@ -1585,113 +1785,13 @@ function parseTableField(t: ParseTree, ctx: ASTCtx): LuaTableField {
|
|
|
1585
1785
|
}
|
|
1586
1786
|
}
|
|
1587
1787
|
|
|
1588
|
-
export function stripLuaComments(s: string): string {
|
|
1589
|
-
let result = "";
|
|
1590
|
-
let i = 0;
|
|
1591
|
-
|
|
1592
|
-
while (i < s.length) {
|
|
1593
|
-
// Check for long string
|
|
1594
|
-
if (s[i] === "[") {
|
|
1595
|
-
let j = i + 1;
|
|
1596
|
-
let equalsCount = 0;
|
|
1597
|
-
while (s[j] === "=") {
|
|
1598
|
-
equalsCount++;
|
|
1599
|
-
j++;
|
|
1600
|
-
}
|
|
1601
|
-
if (s[j] === "[") {
|
|
1602
|
-
// Found long string start
|
|
1603
|
-
const openBracket = s.substring(i, j + 1);
|
|
1604
|
-
const closeBracket = `]${"=".repeat(equalsCount)}]`;
|
|
1605
|
-
result += openBracket;
|
|
1606
|
-
i = j + 1;
|
|
1607
|
-
|
|
1608
|
-
// Find matching closing bracket
|
|
1609
|
-
const content = s.substring(i);
|
|
1610
|
-
const closeIndex = content.indexOf(closeBracket);
|
|
1611
|
-
if (closeIndex !== -1) {
|
|
1612
|
-
// Copy string content verbatim, including any comment-like sequences
|
|
1613
|
-
result += content.substring(0, closeIndex) + closeBracket;
|
|
1614
|
-
i += closeIndex + closeBracket.length;
|
|
1615
|
-
continue;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
// Check for single quoted string
|
|
1621
|
-
if (s[i] === '"' || s[i] === "'") {
|
|
1622
|
-
const quote = s[i];
|
|
1623
|
-
result += quote;
|
|
1624
|
-
i++;
|
|
1625
|
-
while (i < s.length && s[i] !== quote) {
|
|
1626
|
-
if (s[i] === "\\") {
|
|
1627
|
-
result += s[i] + s[i + 1];
|
|
1628
|
-
i += 2;
|
|
1629
|
-
} else {
|
|
1630
|
-
result += s[i];
|
|
1631
|
-
i++;
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
if (i < s.length) {
|
|
1635
|
-
result += s[i]; // closing quote
|
|
1636
|
-
i++;
|
|
1637
|
-
}
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// Check for comments
|
|
1642
|
-
if (s[i] === "-" && s[i + 1] === "-") {
|
|
1643
|
-
// Replace the -- with spaces
|
|
1644
|
-
result += " ";
|
|
1645
|
-
i += 2;
|
|
1646
|
-
|
|
1647
|
-
// Check for long comment
|
|
1648
|
-
if (s[i] === "[") {
|
|
1649
|
-
let j = i + 1;
|
|
1650
|
-
let equalsCount = 0;
|
|
1651
|
-
while (s[j] === "=") {
|
|
1652
|
-
equalsCount++;
|
|
1653
|
-
j++;
|
|
1654
|
-
}
|
|
1655
|
-
if (s[j] === "[") {
|
|
1656
|
-
// Found long comment start
|
|
1657
|
-
const closeBracket = `]${"=".repeat(equalsCount)}]`;
|
|
1658
|
-
// Replace opening bracket with spaces
|
|
1659
|
-
result += " ".repeat(j - i + 1);
|
|
1660
|
-
i = j + 1;
|
|
1661
|
-
|
|
1662
|
-
// Find matching closing bracket
|
|
1663
|
-
const content = s.substring(i);
|
|
1664
|
-
const closeIndex = content.indexOf(closeBracket);
|
|
1665
|
-
if (closeIndex !== -1) {
|
|
1666
|
-
// Replace comment content and closing bracket with spaces
|
|
1667
|
-
result += " ".repeat(closeIndex) + " ".repeat(closeBracket.length);
|
|
1668
|
-
i += closeIndex + closeBracket.length;
|
|
1669
|
-
continue;
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// Single line comment - replace rest of line with spaces
|
|
1675
|
-
while (i < s.length && s[i] !== "\n") {
|
|
1676
|
-
result += " ";
|
|
1677
|
-
i++;
|
|
1678
|
-
}
|
|
1679
|
-
continue;
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
result += s[i];
|
|
1683
|
-
i++;
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
return result;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
1788
|
export function parseBlock(s: string, ctx: ASTCtx = {}): LuaBlock {
|
|
1690
1789
|
try {
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
const result = parseChunk(
|
|
1694
|
-
|
|
1790
|
+
const concreteTree = parseToAST(s);
|
|
1791
|
+
const comments = collectLuaComments(concreteTree, ctx);
|
|
1792
|
+
const result = parseChunk(withoutComments(concreteTree), ctx);
|
|
1793
|
+
assignCommentsToBlocks(result, comments, s);
|
|
1794
|
+
attachFunctionDocumentation(result, comments, s);
|
|
1695
1795
|
getBlockGotoMeta(result);
|
|
1696
1796
|
return result;
|
|
1697
1797
|
} catch (e: any) {
|
|
@@ -1716,7 +1816,25 @@ export function parseToAST(t: string): ParseTree {
|
|
|
1716
1816
|
}
|
|
1717
1817
|
|
|
1718
1818
|
const n = lezerToParseTree(t, tree.topNode);
|
|
1719
|
-
return
|
|
1819
|
+
return cleanLuaTree(n);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function cleanLuaTree(tree: ParseTree): ParseTree {
|
|
1823
|
+
if (tree.type === "⚠") {
|
|
1824
|
+
throw new Error(`Parse error at pos ${tree.from}`);
|
|
1825
|
+
}
|
|
1826
|
+
if (tree.text !== undefined) return tree;
|
|
1827
|
+
const result: ParseTree = {
|
|
1828
|
+
type: tree.type,
|
|
1829
|
+
children: [],
|
|
1830
|
+
from: tree.from,
|
|
1831
|
+
to: tree.to,
|
|
1832
|
+
};
|
|
1833
|
+
for (const node of tree.children ?? []) {
|
|
1834
|
+
if (node.type) result.children!.push(cleanLuaTree(node));
|
|
1835
|
+
if (node.text?.trim()) result.children!.push(node);
|
|
1836
|
+
}
|
|
1837
|
+
return result;
|
|
1720
1838
|
}
|
|
1721
1839
|
|
|
1722
1840
|
function findFirstParseError(node: SyntaxNode): SyntaxNode | null {
|