@reqlan/language 1.6.2 → 1.8.1
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 +18 -0
- package/out/generated/ast.d.ts +31 -4
- package/out/generated/ast.js +28 -7
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.js +2 -3318
- package/out/generated/grammar.js.map +1 -1
- package/out/generated/module.d.ts +2 -2
- package/out/generated/module.js +2 -2
- package/out/generated/module.js.map +1 -1
- package/out/index.d.ts +4 -0
- package/out/index.js +4 -0
- package/out/index.js.map +1 -1
- package/out/reqlan-async-parser.d.ts +37 -0
- package/out/reqlan-async-parser.js +117 -0
- package/out/reqlan-async-parser.js.map +1 -0
- package/out/reqlan-code-action-provider.d.ts +15 -12
- package/out/reqlan-code-action-provider.js +55 -20
- package/out/reqlan-code-action-provider.js.map +1 -1
- package/out/reqlan-comment-resolver.js +5 -0
- package/out/reqlan-comment-resolver.js.map +1 -1
- package/out/reqlan-completion-provider.d.ts +10 -0
- package/out/reqlan-completion-provider.js +64 -30
- package/out/reqlan-completion-provider.js.map +1 -1
- package/out/reqlan-definition-provider.d.ts +7 -0
- package/out/reqlan-definition-provider.js +86 -19
- package/out/reqlan-definition-provider.js.map +1 -1
- package/out/reqlan-document-factory.d.ts +16 -0
- package/out/reqlan-document-factory.js +50 -0
- package/out/reqlan-document-factory.js.map +1 -0
- package/out/reqlan-document-link-provider.d.ts +2 -1
- package/out/reqlan-document-link-provider.js +4 -0
- package/out/reqlan-document-link-provider.js.map +1 -1
- package/out/reqlan-file-link-resolver.d.ts +17 -4
- package/out/reqlan-file-link-resolver.js +89 -14
- package/out/reqlan-file-link-resolver.js.map +1 -1
- package/out/reqlan-import-edits.d.ts +3 -1
- package/out/reqlan-import-edits.js.map +1 -1
- package/out/reqlan-inbound-reference-inlay-label.d.ts +11 -0
- package/out/reqlan-inbound-reference-inlay-label.js +60 -35
- package/out/reqlan-inbound-reference-inlay-label.js.map +1 -1
- package/out/reqlan-inlay-hint-provider.d.ts +6 -0
- package/out/reqlan-inlay-hint-provider.js +34 -6
- package/out/reqlan-inlay-hint-provider.js.map +1 -1
- package/out/reqlan-linker.js +2 -2
- package/out/reqlan-linker.js.map +1 -1
- package/out/reqlan-markdown-links.js +4 -0
- package/out/reqlan-markdown-links.js.map +1 -1
- package/out/reqlan-module.d.ts +2 -1
- package/out/reqlan-module.js +12 -2
- package/out/reqlan-module.js.map +1 -1
- package/out/reqlan-parse-budget.d.ts +50 -0
- package/out/reqlan-parse-budget.js +89 -0
- package/out/reqlan-parse-budget.js.map +1 -0
- package/out/reqlan-parse-worker.d.ts +1 -0
- package/out/reqlan-parse-worker.js +34 -0
- package/out/reqlan-parse-worker.js.map +1 -0
- package/out/reqlan-path-resolve.d.ts +8 -0
- package/out/reqlan-path-resolve.js +18 -4
- package/out/reqlan-path-resolve.js.map +1 -1
- package/out/reqlan-reference-search-site.d.ts +11 -0
- package/out/reqlan-reference-search-site.js +11 -0
- package/out/reqlan-reference-search-site.js.map +1 -1
- package/out/reqlan-scope.d.ts +11 -0
- package/out/reqlan-scope.js +28 -1
- package/out/reqlan-scope.js.map +1 -1
- package/out/reqlan-semantic-token-provider.js +6 -1
- package/out/reqlan-semantic-token-provider.js.map +1 -1
- package/out/reqlan-token-builder.d.ts +3 -2
- package/out/reqlan-token-builder.js +150 -26
- package/out/reqlan-token-builder.js.map +1 -1
- package/out/reqlan-validator.d.ts +10 -1
- package/out/reqlan-validator.js +36 -2
- package/out/reqlan-validator.js.map +1 -1
- package/out/reqlan-wildcard-resolve.d.ts +45 -0
- package/out/reqlan-wildcard-resolve.js +203 -0
- package/out/reqlan-wildcard-resolve.js.map +1 -0
- package/out/reqlan-workspace-manager.d.ts +18 -0
- package/out/reqlan-workspace-manager.js +31 -0
- package/out/reqlan-workspace-manager.js.map +1 -0
- package/package.json +1 -1
- package/src/generated/ast.ts +41 -10
- package/src/generated/grammar.ts +2 -3318
- package/src/generated/module.ts +2 -2
- package/src/index.ts +4 -0
- package/src/reqlan-async-parser.ts +178 -0
- package/src/reqlan-code-action-provider.ts +67 -32
- package/src/reqlan-comment-resolver.ts +5 -0
- package/src/reqlan-completion-provider.ts +82 -28
- package/src/reqlan-definition-provider.ts +91 -18
- package/src/reqlan-document-factory.ts +75 -0
- package/src/reqlan-document-link-provider.ts +9 -1
- package/src/reqlan-file-link-resolver.ts +112 -17
- package/src/reqlan-import-edits.ts +3 -1
- package/src/reqlan-inbound-reference-inlay-label.ts +93 -38
- package/src/reqlan-inlay-hint-provider.ts +36 -6
- package/src/reqlan-linker.ts +4 -2
- package/src/reqlan-markdown-links.ts +4 -0
- package/src/reqlan-module.ts +15 -3
- package/src/reqlan-parse-budget.ts +155 -0
- package/src/reqlan-parse-worker.ts +35 -0
- package/src/reqlan-path-resolve.ts +28 -5
- package/src/reqlan-reference-search-site.ts +25 -0
- package/src/reqlan-scope.ts +34 -1
- package/src/reqlan-semantic-token-provider.ts +6 -0
- package/src/reqlan-token-builder.ts +174 -28
- package/src/reqlan-validator.ts +56 -1
- package/src/reqlan-wildcard-resolve.ts +281 -0
- package/src/reqlan-workspace-manager.ts +47 -0
- package/src/reqlan.langium +17 -8
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve qualified path + idea-pattern wildcard references.
|
|
3
|
+
* rq:["../../../reqlan rq/language/imports.rq".wildcard_references]
|
|
4
|
+
*/
|
|
5
|
+
import type { LangiumDocument, LangiumDocuments } from 'langium';
|
|
6
|
+
import { AstUtils, CstUtils, GrammarUtils, URI, UriUtils } from 'langium';
|
|
7
|
+
import type { Range } from 'vscode-languageserver';
|
|
8
|
+
import {
|
|
9
|
+
isIdea,
|
|
10
|
+
isModel,
|
|
11
|
+
isOneLinerIdea,
|
|
12
|
+
isWildcardReference,
|
|
13
|
+
type IdeaDeclaration,
|
|
14
|
+
type WildcardReference
|
|
15
|
+
} from './generated/ast.js';
|
|
16
|
+
import {
|
|
17
|
+
matchImportRootMapping,
|
|
18
|
+
resolveImportRootUri,
|
|
19
|
+
resolveRqConfig,
|
|
20
|
+
type PathResolveContext
|
|
21
|
+
} from './reqlan-path-resolve.js';
|
|
22
|
+
import { unquoteReqlanString } from './reqlan-quoted-strings.js';
|
|
23
|
+
|
|
24
|
+
export const REQLAN_OPEN_WILDCARD_COMMAND = 'reqlan.openWildcardReference';
|
|
25
|
+
export const REQLAN_WILDCARD_REFERENCE_AT_REQUEST = 'reqlan/wildcardReferenceAt';
|
|
26
|
+
|
|
27
|
+
export interface WildcardMatch {
|
|
28
|
+
fileUri: string;
|
|
29
|
+
ideaName: string;
|
|
30
|
+
range?: Range;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Lightweight catalog entry for index-time expansion without full Langium docs. */
|
|
34
|
+
export interface WildcardIdeaCandidate {
|
|
35
|
+
fileUri: string;
|
|
36
|
+
/** Absolute or workspace path used for path-glob matching. */
|
|
37
|
+
filePath: string;
|
|
38
|
+
ideaName: string;
|
|
39
|
+
range?: Range;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WildcardReferenceArgs {
|
|
43
|
+
pathPattern: string;
|
|
44
|
+
ideaPattern: string;
|
|
45
|
+
fromUri: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function pathHasGlobMeta(path: string): boolean {
|
|
49
|
+
return /[*?]/.test(path) || path.includes('**');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ideaHasGlobMeta(name: string): boolean {
|
|
53
|
+
return /[*?]/.test(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Convert a shell-style glob to an anchored RegExp. */
|
|
57
|
+
export function globToRegExp(glob: string, mode: 'path' | 'name' = 'name'): RegExp {
|
|
58
|
+
let pattern = '';
|
|
59
|
+
for (let i = 0; i < glob.length; i++) {
|
|
60
|
+
const ch = glob[i]!;
|
|
61
|
+
if (ch === '*' && glob[i + 1] === '*') {
|
|
62
|
+
// ** optionally followed by / matches across path segments
|
|
63
|
+
if (glob[i + 2] === '/') {
|
|
64
|
+
pattern += '(?:.*/)?';
|
|
65
|
+
i += 2;
|
|
66
|
+
} else {
|
|
67
|
+
pattern += '.*';
|
|
68
|
+
i += 1;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === '*') {
|
|
73
|
+
pattern += mode === 'path' ? '[^/]*' : '.*';
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (ch === '?') {
|
|
77
|
+
pattern += mode === 'path' ? '[^/]' : '.';
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if ('\\^$+{}[]()|/.'.includes(ch)) {
|
|
81
|
+
pattern += `\\${ch}`;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
pattern += ch;
|
|
85
|
+
}
|
|
86
|
+
return new RegExp(`^${pattern}$`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function wildcardReferenceCommandTarget(args: WildcardReferenceArgs): string {
|
|
90
|
+
return `command:${REQLAN_OPEN_WILDCARD_COMMAND}?${encodeURIComponent(JSON.stringify([args]))}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function wildcardReferenceLabel(pathPattern: string, ideaPattern: string): string {
|
|
94
|
+
return `${pathPattern}.${ideaPattern}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseWildcardPathPattern(pathPatternQuoted: string): string {
|
|
98
|
+
return unquoteReqlanString(pathPatternQuoted);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the path glob against the document directory / import-root alias,
|
|
103
|
+
* returning a regex that matches absolute file paths (posix-style).
|
|
104
|
+
*/
|
|
105
|
+
export function pathPatternToAbsoluteRegex(
|
|
106
|
+
pathPatternQuoted: string,
|
|
107
|
+
document: LangiumDocument,
|
|
108
|
+
context?: PathResolveContext
|
|
109
|
+
): RegExp {
|
|
110
|
+
const raw = parseWildcardPathPattern(pathPatternQuoted);
|
|
111
|
+
const config = resolveRqConfig(document, context);
|
|
112
|
+
const matched = matchImportRootMapping(raw, config.importRoots);
|
|
113
|
+
let absoluteGlob: string;
|
|
114
|
+
if (matched) {
|
|
115
|
+
const importRoot = resolveImportRootUri(document, context, matched.mapping);
|
|
116
|
+
const rootPath = importRoot
|
|
117
|
+
? uriToPath(importRoot)
|
|
118
|
+
: uriToPath(UriUtils.dirname(document.uri));
|
|
119
|
+
absoluteGlob = joinGlobPath(rootPath, matched.remainder);
|
|
120
|
+
} else if (raw.startsWith('/') || /^[A-Za-z]:[\\/]/.test(raw)) {
|
|
121
|
+
absoluteGlob = normalizePathSeparators(raw);
|
|
122
|
+
} else {
|
|
123
|
+
absoluteGlob = joinGlobPath(uriToPath(UriUtils.dirname(document.uri)), raw);
|
|
124
|
+
}
|
|
125
|
+
return globToRegExp(normalizePathSeparators(absoluteGlob), 'path');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function collectIdeaCandidatesFromDocuments(
|
|
129
|
+
documents: Iterable<LangiumDocument>
|
|
130
|
+
): WildcardIdeaCandidate[] {
|
|
131
|
+
const candidates: WildcardIdeaCandidate[] = [];
|
|
132
|
+
for (const document of documents) {
|
|
133
|
+
const model = document.parseResult.value;
|
|
134
|
+
if (!isModel(model)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const fileUri = document.uri.toString();
|
|
138
|
+
const filePath = uriToPath(document.uri);
|
|
139
|
+
for (const idea of collectIdeas(model)) {
|
|
140
|
+
const nameNode = GrammarUtils.findNodeForProperty(idea.$cstNode, 'name');
|
|
141
|
+
candidates.push({
|
|
142
|
+
fileUri,
|
|
143
|
+
filePath,
|
|
144
|
+
ideaName: idea.name,
|
|
145
|
+
range: nameNode?.range
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return candidates;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function matchWildcardAgainstCatalog(
|
|
153
|
+
pathPatternQuoted: string,
|
|
154
|
+
ideaPattern: string,
|
|
155
|
+
document: LangiumDocument,
|
|
156
|
+
candidates: readonly WildcardIdeaCandidate[],
|
|
157
|
+
context?: PathResolveContext
|
|
158
|
+
): WildcardMatch[] {
|
|
159
|
+
const pathRegex = pathPatternToAbsoluteRegex(pathPatternQuoted, document, context);
|
|
160
|
+
const ideaRegex = globToRegExp(ideaPattern, 'name');
|
|
161
|
+
const matches: WildcardMatch[] = [];
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const path = normalizePathSeparators(candidate.filePath);
|
|
164
|
+
if (!pathRegex.test(path)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (!ideaRegex.test(candidate.ideaName)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
matches.push({
|
|
171
|
+
fileUri: candidate.fileUri,
|
|
172
|
+
ideaName: candidate.ideaName,
|
|
173
|
+
range: candidate.range
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return matches.sort((left, right) => {
|
|
177
|
+
const byFile = left.fileUri.localeCompare(right.fileUri);
|
|
178
|
+
return byFile !== 0 ? byFile : left.ideaName.localeCompare(right.ideaName);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function resolveWildcardReferenceMatches(
|
|
183
|
+
reference: WildcardReference,
|
|
184
|
+
documents: LangiumDocuments | Iterable<LangiumDocument>,
|
|
185
|
+
context?: PathResolveContext
|
|
186
|
+
): WildcardMatch[] {
|
|
187
|
+
const document = AstUtils.getDocument(reference);
|
|
188
|
+
const iterable = isLangiumDocuments(documents) ? documents.all : documents;
|
|
189
|
+
const candidates = collectIdeaCandidatesFromDocuments(iterable);
|
|
190
|
+
return matchWildcardAgainstCatalog(
|
|
191
|
+
reference.pathPattern,
|
|
192
|
+
reference.ideaPattern,
|
|
193
|
+
document,
|
|
194
|
+
candidates,
|
|
195
|
+
context
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function wildcardArgsFromReference(reference: WildcardReference): WildcardReferenceArgs {
|
|
200
|
+
const document = AstUtils.getDocument(reference);
|
|
201
|
+
return {
|
|
202
|
+
pathPattern: parseWildcardPathPattern(reference.pathPattern),
|
|
203
|
+
ideaPattern: reference.ideaPattern,
|
|
204
|
+
fromUri: document.uri.toString()
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function findWildcardReferenceAtPosition(
|
|
209
|
+
document: LangiumDocument,
|
|
210
|
+
offset: number
|
|
211
|
+
): WildcardReference | undefined {
|
|
212
|
+
const root = document.parseResult.value.$cstNode;
|
|
213
|
+
if (!root) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
let current = CstUtils.findLeafNodeAtOffset(root, offset)?.astNode;
|
|
217
|
+
while (current) {
|
|
218
|
+
if (isWildcardReference(current)) {
|
|
219
|
+
return current;
|
|
220
|
+
}
|
|
221
|
+
if ('target' in current && isWildcardReference((current as { target?: unknown }).target)) {
|
|
222
|
+
return (current as { target: WildcardReference }).target;
|
|
223
|
+
}
|
|
224
|
+
current = current.$container;
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function collectIdeas(model: { elements?: unknown[] }): IdeaDeclaration[] {
|
|
230
|
+
const ideas: IdeaDeclaration[] = [];
|
|
231
|
+
for (const node of AstUtils.streamAst(model as Parameters<typeof AstUtils.streamAst>[0])) {
|
|
232
|
+
if (isIdea(node) || isOneLinerIdea(node)) {
|
|
233
|
+
ideas.push(node);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return ideas;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isLangiumDocuments(value: unknown): value is LangiumDocuments {
|
|
240
|
+
return typeof value === 'object' && value !== null && 'all' in value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function uriToPath(uri: URI): string {
|
|
244
|
+
if (uri.scheme === 'file') {
|
|
245
|
+
return normalizePathSeparators(uri.fsPath ?? uri.path);
|
|
246
|
+
}
|
|
247
|
+
return normalizePathSeparators(uri.path);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function normalizePathSeparators(path: string): string {
|
|
251
|
+
return path.replace(/\\/g, '/');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function joinGlobPath(base: string, relative: string): string {
|
|
255
|
+
const left = normalizePathSeparators(base).replace(/\/+$/, '');
|
|
256
|
+
const right = normalizePathSeparators(relative).replace(/^\/+/, '');
|
|
257
|
+
return normalizeGlobPath(`${left}/${right}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Collapse `.` / `..` segments without expanding glob metacharacters. */
|
|
261
|
+
function normalizeGlobPath(path: string): string {
|
|
262
|
+
const absolute = path.startsWith('/');
|
|
263
|
+
const parts = path.split('/');
|
|
264
|
+
const out: string[] = [];
|
|
265
|
+
for (const part of parts) {
|
|
266
|
+
if (!part || part === '.') {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (part === '..') {
|
|
270
|
+
if (out.length > 0 && out[out.length - 1] !== '..' && !/[*?]/.test(out[out.length - 1]!)) {
|
|
271
|
+
out.pop();
|
|
272
|
+
} else if (!absolute) {
|
|
273
|
+
out.push(part);
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
out.push(part);
|
|
278
|
+
}
|
|
279
|
+
const joined = out.join('/');
|
|
280
|
+
return absolute ? `/${joined}` : joined;
|
|
281
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DefaultWorkspaceManager,
|
|
3
|
+
type LangiumDocument,
|
|
4
|
+
type LangiumSharedCoreServices,
|
|
5
|
+
type Stream,
|
|
6
|
+
type URI
|
|
7
|
+
} from 'langium';
|
|
8
|
+
import type { RqConfig } from './reqlan-path-resolve.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Loads workspace documents independently so one catastrophic parse failure
|
|
12
|
+
* (for example Chevrotain recovery stack overflow) cannot abort LSP workspace init.
|
|
13
|
+
* rq:["../../../reqlan rq/language/syntax.rq".no_name_idea_safe_warning]
|
|
14
|
+
* rq:["../../../reqlan rq/language/parser_lexer.rq".parse_budget_timeout]
|
|
15
|
+
*/
|
|
16
|
+
export class ReqlanWorkspaceManager extends DefaultWorkspaceManager {
|
|
17
|
+
/**
|
|
18
|
+
* Per-directory cache for resolved `.reqlan/config.json` results.
|
|
19
|
+
* Keyed by directory URI string. `undefined` values mean the directory tree has no `.reqlan/` base.
|
|
20
|
+
* Cleared on workspace reinitialisation so config changes take effect on the next reload.
|
|
21
|
+
*/
|
|
22
|
+
readonly rqConfigCache: Map<string, RqConfig | undefined> = new Map();
|
|
23
|
+
|
|
24
|
+
constructor(services: LangiumSharedCoreServices) {
|
|
25
|
+
super(services);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected override async loadWorkspaceDocuments(
|
|
29
|
+
uris: Stream<URI>,
|
|
30
|
+
collector: (document: LangiumDocument) => void
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
this.rqConfigCache.clear();
|
|
33
|
+
await Promise.all(
|
|
34
|
+
uris.map(async uri => {
|
|
35
|
+
try {
|
|
36
|
+
const document = await this.langiumDocuments.getOrCreateDocument(uri);
|
|
37
|
+
collector(document);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(
|
|
40
|
+
`[reqlan] Skipping document that failed to load during workspace init: ${uri.toString()}`,
|
|
41
|
+
error
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/reqlan.langium
CHANGED
|
@@ -17,8 +17,10 @@ entry Model:
|
|
|
17
17
|
(NL* elements+=TopLevelElement)*
|
|
18
18
|
NL*;
|
|
19
19
|
|
|
20
|
+
// Nameless `{ ... }` at top level is recoverable (no_name_idea_safe_warning):
|
|
21
|
+
// parse it, warn, and keep later ideas/imports intact — do not abort the file.
|
|
20
22
|
TopLevelElement:
|
|
21
|
-
Idea | IdeaSet | OneLinerIdea;
|
|
23
|
+
Idea | IdeaSet | OneLinerIdea | AnonymousBlock;
|
|
22
24
|
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
26
|
// idea_name, one_liner_idea, block_idea, ideaset
|
|
@@ -78,11 +80,11 @@ CodeSnippet:
|
|
|
78
80
|
// ---------------------------------------------------------------------------
|
|
79
81
|
|
|
80
82
|
OneLinerText returns string:
|
|
81
|
-
WORD | ID | NUMBER | INLINE_CODE | OTHER | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
|
|
83
|
+
WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
|
|
82
84
|
|
|
83
85
|
// PlainText must not include INLINE_CODE — that terminal is its own RichTextPart/InlineTextPart alt.
|
|
84
86
|
PlainText returns string:
|
|
85
|
-
WORD | ID | NUMBER | OTHER | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
87
|
+
WORD | ID | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
86
88
|
|
|
87
89
|
PlainPunctuation returns string:
|
|
88
90
|
'.' | ',' | ':' | ';' | '!' | '?' | '-';
|
|
@@ -111,7 +113,7 @@ NonBangInlineTextPart:
|
|
|
111
113
|
|
|
112
114
|
// No '!' (negated flag) and no leading ':' (attribute separator).
|
|
113
115
|
NonBangPlainText returns string:
|
|
114
|
-
WORD | ID | NUMBER | OTHER | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
116
|
+
WORD | ID | NUMBER | OTHER | WILDCARD_NAME | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
115
117
|
|
|
116
118
|
NonBangPunctuation returns string:
|
|
117
119
|
'.' | ',' | ';' | '?' | '-';
|
|
@@ -150,7 +152,7 @@ ListItemBody:
|
|
|
150
152
|
content+=(MarkdownLink | BracketReference | WikiLink | ListItemText)+;
|
|
151
153
|
|
|
152
154
|
ListItemText returns string:
|
|
153
|
-
WORD | ID | NUMBER | INLINE_CODE | OTHER | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
|
|
155
|
+
WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
|
|
154
156
|
|
|
155
157
|
ListItemPunctuation returns string:
|
|
156
158
|
'.' | ':' | ';' | '!' | '?' | '-';
|
|
@@ -178,12 +180,13 @@ BracketReference:
|
|
|
178
180
|
'[' target=ReferenceTarget ']';
|
|
179
181
|
|
|
180
182
|
// Order and shape chosen so alternatives do not share a common LL prefix:
|
|
183
|
+
// - WildcardReference is STRING.WILDCARD_NAME (idea pattern must contain * or ?)
|
|
181
184
|
// - QualifiedReference covers STRING.ID / ID.ID (incl. idea.attribute surface form)
|
|
182
185
|
// - FileSymbolReference only for STRING.ID.ID.ID+ (3+ symbol segments)
|
|
183
186
|
// - FileReference is bare STRING
|
|
184
187
|
// - LocalReference is a bare local name
|
|
185
188
|
ReferenceTarget:
|
|
186
|
-
QualifiedReference | FileSymbolReference | FileReference | LocalReference;
|
|
189
|
+
WildcardReference | QualifiedReference | FileSymbolReference | FileReference | LocalReference;
|
|
187
190
|
|
|
188
191
|
FileReference:
|
|
189
192
|
file=STRING;
|
|
@@ -191,6 +194,10 @@ FileReference:
|
|
|
191
194
|
FileSymbolReference:
|
|
192
195
|
file=STRING '.' symbols+=ID '.' symbols+=ID '.' symbols+=ID ('.' symbols+=ID)*;
|
|
193
196
|
|
|
197
|
+
// Path may include globs (* ** ?); ideaPattern must include * or ? so exact qualified refs stay QualifiedReference.
|
|
198
|
+
WildcardReference:
|
|
199
|
+
pathPattern=STRING '.' ideaPattern=WILDCARD_NAME;
|
|
200
|
+
|
|
194
201
|
QualifiedReference:
|
|
195
202
|
(path=[Import:STRING] | qualifier=[Import:ID]) '.'
|
|
196
203
|
(ideaset=[IdeaSet:ReferenceName] '.')? idea=[IdeaDeclaration:ReferenceName];
|
|
@@ -232,10 +239,10 @@ InvalidFromImport:
|
|
|
232
239
|
'from' head=InvalidImportHeadToken tokens+=InvalidImportToken*;
|
|
233
240
|
|
|
234
241
|
InvalidImportHeadToken returns string:
|
|
235
|
-
ID | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
242
|
+
ID | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
236
243
|
|
|
237
244
|
InvalidImportToken returns string:
|
|
238
|
-
ID | STRING | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
245
|
+
ID | STRING | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
239
246
|
|
|
240
247
|
// ---------------------------------------------------------------------------
|
|
241
248
|
// comments, string_literals
|
|
@@ -248,6 +255,8 @@ hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
|
|
|
248
255
|
|
|
249
256
|
terminal MARKDOWN_LINK: /\[(?!\[)[^\]]+\]\([^)]+\)/;
|
|
250
257
|
terminal ID: /[_a-zA-Z][\w-]*/;
|
|
258
|
+
// Must contain * or ? so it does not steal ordinary ID tokens.
|
|
259
|
+
terminal WILDCARD_NAME: /\*[\w*?-]*|\?[\w*?-]*|[_a-zA-Z][\w-]*[*?][\w*?-]*/;
|
|
251
260
|
terminal STRING: /"(\\.|[^"\\\r\n])*"|'(\\.|[^'\\\r\n])*'/;
|
|
252
261
|
terminal NUMBER: /\d+/;
|
|
253
262
|
terminal WORD: /[A-Za-z_][\w']*/;
|