@shapeshift-labs/frontier-lang-compiler 0.2.1 → 0.2.3
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/bench/smoke.mjs +25 -0
- package/benchmarks/package-bench.mjs +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +369 -9
- package/package.json +13 -7
package/bench/smoke.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { compileFrontierSource } from '../dist/index.js';
|
|
3
|
+
|
|
4
|
+
const source = `
|
|
5
|
+
module Bench @id("mod_bench")
|
|
6
|
+
type TodoInput @id("type_input") {
|
|
7
|
+
title: Text
|
|
8
|
+
}
|
|
9
|
+
entity Todo @id("ent_todo") {
|
|
10
|
+
title @id("field_title"): Text
|
|
11
|
+
}
|
|
12
|
+
action addTodo @id("action_add") {
|
|
13
|
+
input TodoInput
|
|
14
|
+
writes field_title
|
|
15
|
+
returns Patch
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const targets = ['typescript', 'javascript', 'rust', 'python', 'c'];
|
|
20
|
+
const start = performance.now();
|
|
21
|
+
let bytes = 0;
|
|
22
|
+
for (let index = 0; index < 250; index += 1) {
|
|
23
|
+
bytes += compileFrontierSource(source, { target: targets[index % targets.length] }).output.length;
|
|
24
|
+
}
|
|
25
|
+
console.log(JSON.stringify({ compiles: 250, bytes, durationMs: Number((performance.now() - start).toFixed(2)) }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../bench/smoke.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,12 +4,14 @@ import type {
|
|
|
4
4
|
CompileTarget,
|
|
5
5
|
EvidenceRecord,
|
|
6
6
|
FrontierLangDocument,
|
|
7
|
+
FrontierUniversalAstEnvelope,
|
|
7
8
|
FrontierSourceLanguage,
|
|
8
9
|
LanguageImportResult,
|
|
9
10
|
NativeAstLossRecord,
|
|
10
11
|
NativeAstNode,
|
|
11
12
|
NativeAstRecord,
|
|
12
13
|
NativeSourceNode,
|
|
14
|
+
SemanticIndexRecord,
|
|
13
15
|
SemanticNode,
|
|
14
16
|
SemanticPatchBundle
|
|
15
17
|
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
@@ -73,6 +75,7 @@ export interface ImportNativeSourceOptions {
|
|
|
73
75
|
readonly parserVersion?: string;
|
|
74
76
|
readonly sourcePath?: string;
|
|
75
77
|
readonly sourceHash?: string;
|
|
78
|
+
readonly sourceText?: string;
|
|
76
79
|
readonly symbol?: string;
|
|
77
80
|
readonly name?: string;
|
|
78
81
|
readonly nativeAst?: NativeAstRecord;
|
|
@@ -97,11 +100,16 @@ export interface ImportNativeSourceOptions {
|
|
|
97
100
|
readonly patchId?: string;
|
|
98
101
|
readonly author?: string;
|
|
99
102
|
readonly target?: CompileTarget;
|
|
103
|
+
readonly semanticIndex?: SemanticIndexRecord;
|
|
104
|
+
readonly universalAstId?: string;
|
|
105
|
+
readonly universalAstMetadata?: Record<string, unknown>;
|
|
100
106
|
readonly metadata?: Record<string, unknown>;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export type NativeSourceImportResult = LanguageImportResult & {
|
|
104
110
|
readonly nativeSource: NativeSourceNode;
|
|
111
|
+
readonly semanticIndex?: SemanticIndexRecord;
|
|
112
|
+
readonly universalAst: FrontierUniversalAstEnvelope;
|
|
105
113
|
};
|
|
106
114
|
|
|
107
115
|
export declare const FrontierCompileTargets: readonly FrontierCompileTarget[];
|
|
@@ -112,4 +120,12 @@ export declare function projectFrontierAst(document: FrontierLangDocument, targe
|
|
|
112
120
|
export declare function renderTargetAst(ast: FrontierTargetAst, target?: FrontierCompileOptions['target']): string;
|
|
113
121
|
export declare function resolveCapabilityAdapters(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: { readonly platform?: string }): readonly CapabilityResolution[];
|
|
114
122
|
export declare function importNativeSource(input: ImportNativeSourceOptions): NativeSourceImportResult;
|
|
123
|
+
export declare function createUniversalAstFromDocument(document: FrontierLangDocument, input?: {
|
|
124
|
+
readonly id?: string;
|
|
125
|
+
readonly semanticIndex?: SemanticIndexRecord;
|
|
126
|
+
readonly evidence?: readonly EvidenceRecord[];
|
|
127
|
+
readonly metadata?: Record<string, unknown>;
|
|
128
|
+
}): FrontierUniversalAstEnvelope;
|
|
129
|
+
export declare function readUniversalAstJson(source: string): FrontierUniversalAstEnvelope;
|
|
130
|
+
export declare function writeUniversalAstJson(envelope: FrontierUniversalAstEnvelope): string;
|
|
115
131
|
export declare function emitForTarget(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: FrontierCompileEmitOptions): string;
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,13 @@ import {
|
|
|
3
3
|
createImportResult,
|
|
4
4
|
createNativeAstRecord,
|
|
5
5
|
createPatch,
|
|
6
|
+
createSemanticIndexRecord,
|
|
7
|
+
createUniversalAstEnvelope,
|
|
6
8
|
hashDocumentBase,
|
|
7
9
|
hashSemanticValue,
|
|
8
|
-
nativeSourceNode
|
|
10
|
+
nativeSourceNode,
|
|
11
|
+
stableUniversalAstJson,
|
|
12
|
+
validateUniversalAstEnvelope
|
|
9
13
|
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
10
14
|
import { parseFrontierFile, parseFrontierSource } from '@shapeshift-labs/frontier-lang-parser';
|
|
11
15
|
import { checkDocument } from '@shapeshift-labs/frontier-lang-checker';
|
|
@@ -135,17 +139,26 @@ export function importNativeSource(input) {
|
|
|
135
139
|
const language = input.language ?? input.nativeAst?.language;
|
|
136
140
|
if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
|
|
137
141
|
const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
|
|
138
|
-
const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {});
|
|
142
|
+
const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
|
|
139
143
|
const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
|
|
144
|
+
const lightweight = !input.nativeAst && !input.nodes && input.sourceText
|
|
145
|
+
? createLightweightNativeImport({
|
|
146
|
+
language,
|
|
147
|
+
sourceText: input.sourceText,
|
|
148
|
+
sourcePath,
|
|
149
|
+
sourceHash,
|
|
150
|
+
parser: input.parser
|
|
151
|
+
})
|
|
152
|
+
: undefined;
|
|
140
153
|
const nativeAst = input.nativeAst ?? createNativeAstRecord({
|
|
141
154
|
id: input.nativeAstId ?? `native_ast_${importIdPart}`,
|
|
142
155
|
language,
|
|
143
|
-
parser: input.parser,
|
|
156
|
+
parser: input.parser ?? lightweight?.parser,
|
|
144
157
|
parserVersion: input.parserVersion,
|
|
145
158
|
sourcePath,
|
|
146
159
|
sourceHash,
|
|
147
|
-
rootId: input.rootId ?? 'native_root',
|
|
148
|
-
nodes: input.nodes ?? {
|
|
160
|
+
rootId: input.rootId ?? lightweight?.rootId ?? 'native_root',
|
|
161
|
+
nodes: input.nodes ?? lightweight?.nodes ?? {
|
|
149
162
|
native_root: {
|
|
150
163
|
id: 'native_root',
|
|
151
164
|
kind: 'Unknown',
|
|
@@ -154,11 +167,15 @@ export function importNativeSource(input) {
|
|
|
154
167
|
metadata: { reason: 'no-native-ast-nodes-provided' }
|
|
155
168
|
}
|
|
156
169
|
},
|
|
157
|
-
losses: input.losses,
|
|
158
|
-
metadata:
|
|
170
|
+
losses: input.losses ?? lightweight?.losses,
|
|
171
|
+
metadata: {
|
|
172
|
+
...(input.sourceText ? { sourceBytes: input.sourceText.length } : {}),
|
|
173
|
+
...lightweight?.metadata,
|
|
174
|
+
...input.nativeAstMetadata
|
|
175
|
+
}
|
|
159
176
|
});
|
|
160
177
|
const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
|
|
161
|
-
const losses = input.losses ?? nativeAst.losses ?? [];
|
|
178
|
+
const losses = input.losses ?? nativeAst.losses ?? lightweight?.losses ?? [];
|
|
162
179
|
const nativeSource = nativeSourceNode({
|
|
163
180
|
id: input.nativeSourceId ?? `native_source_${importIdPart}`,
|
|
164
181
|
name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
|
|
@@ -202,6 +219,21 @@ export function importNativeSource(input) {
|
|
|
202
219
|
semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only')
|
|
203
220
|
}
|
|
204
221
|
}];
|
|
222
|
+
const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
|
|
223
|
+
const universalAst = createUniversalAstEnvelope({
|
|
224
|
+
id: input.universalAstId ?? `universal_ast_${importIdPart}`,
|
|
225
|
+
document,
|
|
226
|
+
nativeSources: [nativeSource],
|
|
227
|
+
semanticIndex,
|
|
228
|
+
losses,
|
|
229
|
+
evidence,
|
|
230
|
+
metadata: {
|
|
231
|
+
sourceLanguage: language,
|
|
232
|
+
sourcePath,
|
|
233
|
+
semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
|
|
234
|
+
...input.universalAstMetadata
|
|
235
|
+
}
|
|
236
|
+
});
|
|
205
237
|
const patch = input.patch ?? createPatch({
|
|
206
238
|
id: input.patchId ?? `patch_${importIdPart}_import`,
|
|
207
239
|
author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeSource',
|
|
@@ -212,7 +244,7 @@ export function importNativeSource(input) {
|
|
|
212
244
|
touches: [{ id: node.id, access: node.kind === 'nativeSource' ? 'evidence' : 'schema' }]
|
|
213
245
|
})),
|
|
214
246
|
evidence,
|
|
215
|
-
metadata: { sourceLanguage: language, sourcePath }
|
|
247
|
+
metadata: { sourceLanguage: language, sourcePath, semanticIndexId: semanticIndex?.id, universalAstId: universalAst.id }
|
|
216
248
|
});
|
|
217
249
|
return {
|
|
218
250
|
...createImportResult({
|
|
@@ -222,10 +254,14 @@ export function importNativeSource(input) {
|
|
|
222
254
|
document,
|
|
223
255
|
patch,
|
|
224
256
|
nativeAst,
|
|
257
|
+
semanticIndex,
|
|
258
|
+
universalAst,
|
|
225
259
|
losses,
|
|
226
260
|
evidence,
|
|
227
261
|
metadata: {
|
|
228
262
|
nativeSourceId: nativeSource.id,
|
|
263
|
+
semanticIndexId: semanticIndex?.id,
|
|
264
|
+
universalAstId: universalAst.id,
|
|
229
265
|
semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
|
|
230
266
|
mappings: input.mappings ?? [],
|
|
231
267
|
...input.metadata
|
|
@@ -235,6 +271,330 @@ export function importNativeSource(input) {
|
|
|
235
271
|
};
|
|
236
272
|
}
|
|
237
273
|
|
|
274
|
+
function createLightweightNativeImport(input) {
|
|
275
|
+
const parser = input.parser ?? `${input.language}.lightweight-declaration-scan`;
|
|
276
|
+
const rootId = 'native_root';
|
|
277
|
+
const nodes = {
|
|
278
|
+
[rootId]: {
|
|
279
|
+
id: rootId,
|
|
280
|
+
kind: 'Program',
|
|
281
|
+
languageKind: `${input.language}.program`,
|
|
282
|
+
children: [],
|
|
283
|
+
metadata: { parser, sourceHash: input.sourceHash }
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const declarations = scanNativeDeclarations(input);
|
|
287
|
+
const losses = [];
|
|
288
|
+
const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}`;
|
|
289
|
+
const symbols = [];
|
|
290
|
+
const occurrences = [];
|
|
291
|
+
const relations = [];
|
|
292
|
+
const facts = [];
|
|
293
|
+
|
|
294
|
+
for (const declaration of declarations) {
|
|
295
|
+
nodes[rootId].children.push(declaration.nodeId);
|
|
296
|
+
nodes[declaration.nodeId] = {
|
|
297
|
+
id: declaration.nodeId,
|
|
298
|
+
kind: declaration.kind,
|
|
299
|
+
languageKind: declaration.languageKind,
|
|
300
|
+
span: declaration.span,
|
|
301
|
+
value: declaration.name ?? declaration.importPath ?? null,
|
|
302
|
+
fields: declaration.fields,
|
|
303
|
+
metadata: declaration.metadata
|
|
304
|
+
};
|
|
305
|
+
if (declaration.symbolId) {
|
|
306
|
+
symbols.push({
|
|
307
|
+
id: declaration.symbolId,
|
|
308
|
+
scheme: 'frontier',
|
|
309
|
+
name: declaration.name,
|
|
310
|
+
kind: declaration.symbolKind,
|
|
311
|
+
language: input.language,
|
|
312
|
+
nativeAstNodeId: declaration.nodeId,
|
|
313
|
+
signatureHash: hashSemanticValue([input.language, declaration.kind, declaration.name, declaration.fields ?? {}]),
|
|
314
|
+
definitionSpan: declaration.span
|
|
315
|
+
});
|
|
316
|
+
occurrences.push({
|
|
317
|
+
id: `occ_${idFragment(declaration.nodeId)}_def`,
|
|
318
|
+
documentId,
|
|
319
|
+
symbolId: declaration.symbolId,
|
|
320
|
+
role: declaration.role ?? 'definition',
|
|
321
|
+
span: declaration.span,
|
|
322
|
+
nativeAstNodeId: declaration.nodeId
|
|
323
|
+
});
|
|
324
|
+
relations.push({
|
|
325
|
+
id: `rel_${idFragment(documentId)}_${idFragment(declaration.nodeId)}`,
|
|
326
|
+
sourceId: documentId,
|
|
327
|
+
predicate: declaration.role === 'import' ? 'imports' : 'defines',
|
|
328
|
+
targetId: declaration.symbolId
|
|
329
|
+
});
|
|
330
|
+
facts.push({
|
|
331
|
+
id: `fact_${idFragment(declaration.nodeId)}_kind`,
|
|
332
|
+
predicate: 'nativeKind',
|
|
333
|
+
subjectId: declaration.symbolId,
|
|
334
|
+
value: declaration.languageKind
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (declaration.loss) losses.push(declaration.loss);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const semanticIndex = createSemanticIndexRecord({
|
|
341
|
+
id: `index_${idFragment(input.sourcePath ?? input.language)}`,
|
|
342
|
+
documents: [{
|
|
343
|
+
id: documentId,
|
|
344
|
+
path: input.sourcePath ?? `${input.language}:memory`,
|
|
345
|
+
language: input.language,
|
|
346
|
+
sourceHash: input.sourceHash
|
|
347
|
+
}],
|
|
348
|
+
symbols,
|
|
349
|
+
occurrences,
|
|
350
|
+
relations,
|
|
351
|
+
facts,
|
|
352
|
+
evidence: [{
|
|
353
|
+
id: `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`,
|
|
354
|
+
kind: 'import',
|
|
355
|
+
status: 'passed',
|
|
356
|
+
path: input.sourcePath,
|
|
357
|
+
summary: `Lightweight declaration scan found ${symbols.length} symbol(s).`,
|
|
358
|
+
metadata: { parser }
|
|
359
|
+
}],
|
|
360
|
+
metadata: {
|
|
361
|
+
parser,
|
|
362
|
+
coverage: 'declarations-only',
|
|
363
|
+
unsupported: ['full expression AST', 'type checking', 'control flow', 'comments and formatting preservation']
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
parser,
|
|
369
|
+
rootId,
|
|
370
|
+
nodes,
|
|
371
|
+
losses,
|
|
372
|
+
semanticIndex,
|
|
373
|
+
metadata: { parser, scanKind: 'lightweight-declaration-scan', declarationCount: declarations.length }
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function scanNativeDeclarations(input) {
|
|
378
|
+
const language = String(input.language).toLowerCase();
|
|
379
|
+
if (language === 'javascript' || language === 'typescript') return scanJavaScriptLike(input);
|
|
380
|
+
if (language === 'python') return scanPython(input);
|
|
381
|
+
if (language === 'rust') return scanRust(input);
|
|
382
|
+
if (language === 'c' || language === 'cpp' || language === 'c++') return scanCLike(input);
|
|
383
|
+
return scanGenericDeclarations(input);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function scanJavaScriptLike(input) {
|
|
387
|
+
const declarations = [];
|
|
388
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
389
|
+
const trimmed = line.trim();
|
|
390
|
+
let match;
|
|
391
|
+
if ((match = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
|
|
392
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
393
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
|
|
394
|
+
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, trimmed.includes('{')));
|
|
395
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/))) {
|
|
396
|
+
declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, trimmed.includes('{')));
|
|
397
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/))) {
|
|
398
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
|
|
399
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
|
|
400
|
+
declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
401
|
+
} else if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
|
|
402
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
|
|
403
|
+
} else if ((match = trimmed.match(/^export\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/))) {
|
|
404
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ExportFromDeclaration', 'module'));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return declarations;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function scanPython(input) {
|
|
411
|
+
const declarations = [];
|
|
412
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
413
|
+
const trimmed = line.trim();
|
|
414
|
+
let match;
|
|
415
|
+
if ((match = trimmed.match(/^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*:/))) {
|
|
416
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDef', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
417
|
+
} else if ((match = trimmed.match(/^class\s+([A-Za-z_]\w*)/))) {
|
|
418
|
+
declarations.push(nativeDeclaration(input, number, 'ClassDef', 'class', match[1], {}, true));
|
|
419
|
+
} else if ((match = trimmed.match(/^(?:from\s+([A-Za-z_][\w.]*)\s+import\s+.+|import\s+([A-Za-z_][\w.]*))/))) {
|
|
420
|
+
declarations.push(nativeImportDeclaration(input, number, match[1] ?? match[2], 'Import', 'module'));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return declarations;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function scanRust(input) {
|
|
427
|
+
const declarations = [];
|
|
428
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
429
|
+
const trimmed = line.trim();
|
|
430
|
+
let match;
|
|
431
|
+
if ((match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
432
|
+
declarations.push(nativeDeclaration(input, number, 'ItemFn', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
433
|
+
} else if ((match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/))) {
|
|
434
|
+
declarations.push(nativeDeclaration(input, number, 'ItemStruct', 'type', match[1], {}, trimmed.includes('{')));
|
|
435
|
+
} else if ((match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/))) {
|
|
436
|
+
declarations.push(nativeDeclaration(input, number, 'ItemEnum', 'type', match[1], {}, trimmed.includes('{')));
|
|
437
|
+
} else if ((match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?trait\s+([A-Za-z_]\w*)/))) {
|
|
438
|
+
declarations.push(nativeDeclaration(input, number, 'ItemTrait', 'trait', match[1], {}, trimmed.includes('{')));
|
|
439
|
+
} else if ((match = trimmed.match(/^impl(?:\s*<[^>]+>)?\s+(.+?)\s*\{/))) {
|
|
440
|
+
declarations.push(nativeDeclaration(input, number, 'ItemImpl', 'implementation', idFragment(match[1]), { target: match[1].trim() }, true));
|
|
441
|
+
} else if ((match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/))) {
|
|
442
|
+
declarations.push(nativeDeclaration(input, number, 'ItemMod', 'module', match[1], {}, trimmed.includes('{')));
|
|
443
|
+
} else if ((match = trimmed.match(/^use\s+(.+?);/))) {
|
|
444
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ItemUse', 'module'));
|
|
445
|
+
} else if (/^[A-Za-z_]\w*!\s*[({[]/.test(trimmed)) {
|
|
446
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion'));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return declarations;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function scanCLike(input) {
|
|
453
|
+
const declarations = [];
|
|
454
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
455
|
+
const trimmed = line.trim();
|
|
456
|
+
let match;
|
|
457
|
+
if ((match = trimmed.match(/^#\s*include\s+[<"]([^>"]+)[>"]/))) {
|
|
458
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'IncludeDirective', 'header'));
|
|
459
|
+
} else if ((match = trimmed.match(/^#\s*define\s+([A-Za-z_]\w*)/))) {
|
|
460
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', match[1]));
|
|
461
|
+
} else if ((match = trimmed.match(/^typedef\s+struct(?:\s+([A-Za-z_]\w*))?/))) {
|
|
462
|
+
declarations.push(nativeDeclaration(input, number, 'TypedefStructDeclaration', 'type', match[1] ?? `anonymous_struct_${number}`, {}, trimmed.includes('{')));
|
|
463
|
+
} else if ((match = trimmed.match(/^(?:struct|enum)\s+([A-Za-z_]\w*)/))) {
|
|
464
|
+
declarations.push(nativeDeclaration(input, number, 'TagDeclaration', 'type', match[1], {}, trimmed.includes('{')));
|
|
465
|
+
} else if ((match = trimmed.match(/^(?:[A-Za-z_][\w\s*:&<>]+)\s+([A-Za-z_]\w*)\s*\(([^;{}]*)\)\s*(?:;|\{)?$/))) {
|
|
466
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.endsWith('{')));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return declarations;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function scanGenericDeclarations(input) {
|
|
473
|
+
return sourceLines(input.sourceText)
|
|
474
|
+
.filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
|
|
475
|
+
.map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
|
|
479
|
+
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
480
|
+
return {
|
|
481
|
+
nodeId,
|
|
482
|
+
kind: languageKind,
|
|
483
|
+
languageKind: `${input.language}.${languageKind}`,
|
|
484
|
+
name,
|
|
485
|
+
symbolKind,
|
|
486
|
+
symbolId: `symbol:${input.language}:${idFragment(name)}`,
|
|
487
|
+
span: spanForLine(input, lineNumber),
|
|
488
|
+
fields,
|
|
489
|
+
metadata: { scan: 'lightweight-declaration', hasBody },
|
|
490
|
+
...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function nativeImportDeclaration(input, lineNumber, importPath, languageKind, symbolKind) {
|
|
495
|
+
const name = String(importPath);
|
|
496
|
+
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
497
|
+
return {
|
|
498
|
+
nodeId,
|
|
499
|
+
kind: languageKind,
|
|
500
|
+
languageKind: `${input.language}.${languageKind}`,
|
|
501
|
+
name,
|
|
502
|
+
symbolKind,
|
|
503
|
+
symbolId: `symbol:${input.language}:import:${idFragment(name)}`,
|
|
504
|
+
role: 'import',
|
|
505
|
+
importPath: name,
|
|
506
|
+
span: spanForLine(input, lineNumber),
|
|
507
|
+
fields: { importPath: name },
|
|
508
|
+
metadata: { scan: 'lightweight-import' }
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function nativeMacroLoss(input, lineNumber, source, kind, name = idFragment(source).slice(0, 40)) {
|
|
513
|
+
const nodeId = `native_${kind}_${lineNumber}_${idFragment(name)}`;
|
|
514
|
+
return {
|
|
515
|
+
nodeId,
|
|
516
|
+
kind: kind === 'preprocessor' ? 'PreprocessorDirective' : 'MacroInvocation',
|
|
517
|
+
languageKind: `${input.language}.${kind}`,
|
|
518
|
+
name,
|
|
519
|
+
symbolKind: 'constant',
|
|
520
|
+
symbolId: `symbol:${input.language}:${kind}:${idFragment(name)}`,
|
|
521
|
+
span: spanForLine(input, lineNumber),
|
|
522
|
+
fields: { source },
|
|
523
|
+
metadata: { scan: 'lightweight-macro' },
|
|
524
|
+
loss: {
|
|
525
|
+
id: `loss_${idFragment(nodeId)}`,
|
|
526
|
+
severity: 'warning',
|
|
527
|
+
phase: 'read',
|
|
528
|
+
sourceFormat: input.language,
|
|
529
|
+
kind,
|
|
530
|
+
message: `${input.language} ${kind} retained as native source; expansion is not evaluated by the lightweight importer.`,
|
|
531
|
+
span: spanForLine(input, lineNumber),
|
|
532
|
+
nodeId
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function opaqueBodyLoss(input, lineNumber, nodeId, name) {
|
|
538
|
+
return {
|
|
539
|
+
id: `loss_${idFragment(nodeId)}_body`,
|
|
540
|
+
severity: 'info',
|
|
541
|
+
phase: 'read',
|
|
542
|
+
sourceFormat: input.language,
|
|
543
|
+
kind: 'opaqueNative',
|
|
544
|
+
message: `Body for ${name} is retained as native source by the lightweight declaration importer.`,
|
|
545
|
+
span: spanForLine(input, lineNumber),
|
|
546
|
+
nodeId
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function sourceLines(sourceText) {
|
|
551
|
+
return String(sourceText ?? '').split(/\r?\n/).map((line, index) => ({ line, number: index + 1 }));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function spanForLine(input, lineNumber) {
|
|
555
|
+
return {
|
|
556
|
+
sourceId: input.sourceHash,
|
|
557
|
+
path: input.sourcePath,
|
|
558
|
+
startLine: lineNumber,
|
|
559
|
+
endLine: lineNumber,
|
|
560
|
+
startColumn: 1
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function splitParameters(raw) {
|
|
565
|
+
return String(raw ?? '')
|
|
566
|
+
.split(',')
|
|
567
|
+
.map((part) => part.trim())
|
|
568
|
+
.filter(Boolean);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function createUniversalAstFromDocument(document, input = {}) {
|
|
572
|
+
return createUniversalAstEnvelope({
|
|
573
|
+
id: input.id ?? `universal_ast_${idFragment(document.id)}`,
|
|
574
|
+
document,
|
|
575
|
+
semanticIndex: input.semanticIndex,
|
|
576
|
+
evidence: input.evidence ?? [],
|
|
577
|
+
metadata: input.metadata
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function readUniversalAstJson(source) {
|
|
582
|
+
const envelope = JSON.parse(source);
|
|
583
|
+
const issues = validateUniversalAstEnvelope(envelope);
|
|
584
|
+
if (issues.length > 0) {
|
|
585
|
+
throw new Error(`Invalid Frontier universal AST JSON: ${issues.join('; ')}`);
|
|
586
|
+
}
|
|
587
|
+
return envelope;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function writeUniversalAstJson(envelope) {
|
|
591
|
+
const issues = validateUniversalAstEnvelope(envelope);
|
|
592
|
+
if (issues.length > 0) {
|
|
593
|
+
throw new Error(`Invalid Frontier universal AST envelope: ${issues.join('; ')}`);
|
|
594
|
+
}
|
|
595
|
+
return stableUniversalAstJson(envelope);
|
|
596
|
+
}
|
|
597
|
+
|
|
238
598
|
export function emitForTarget(document, target = 'typescript', options = {}) {
|
|
239
599
|
return renderTargetAst(projectFrontierAst(document, target, options), target);
|
|
240
600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapeshift-labs/frontier-lang-compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
"dist",
|
|
17
17
|
"examples",
|
|
18
18
|
"README.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"bench",
|
|
21
|
+
"benchmarks/package-bench.mjs"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
24
|
"build": "node scripts/build.mjs",
|
|
23
25
|
"test": "npm run build && node test/smoke.mjs",
|
|
26
|
+
"typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
|
|
24
27
|
"fuzz": "npm run build && node fuzz/smoke.mjs",
|
|
25
28
|
"bench": "npm run build && node bench/smoke.mjs",
|
|
26
29
|
"prepare": "npm run build",
|
|
@@ -53,13 +56,16 @@
|
|
|
53
56
|
"access": "public"
|
|
54
57
|
},
|
|
55
58
|
"dependencies": {
|
|
56
|
-
"@shapeshift-labs/frontier-lang-
|
|
57
|
-
"@shapeshift-labs/frontier-lang-parser": "0.3.0",
|
|
59
|
+
"@shapeshift-labs/frontier-lang-c": "0.2.0",
|
|
58
60
|
"@shapeshift-labs/frontier-lang-checker": "0.3.0",
|
|
59
|
-
"@shapeshift-labs/frontier-lang-typescript": "0.3.0",
|
|
60
61
|
"@shapeshift-labs/frontier-lang-javascript": "0.2.0",
|
|
61
|
-
"@shapeshift-labs/frontier-lang-
|
|
62
|
+
"@shapeshift-labs/frontier-lang-kernel": "0.3.1",
|
|
63
|
+
"@shapeshift-labs/frontier-lang-parser": "0.3.0",
|
|
62
64
|
"@shapeshift-labs/frontier-lang-python": "0.2.0",
|
|
63
|
-
"@shapeshift-labs/frontier-lang-
|
|
65
|
+
"@shapeshift-labs/frontier-lang-rust": "0.2.0",
|
|
66
|
+
"@shapeshift-labs/frontier-lang-typescript": "0.3.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"typescript": "^5.9.3"
|
|
64
70
|
}
|
|
65
71
|
}
|