@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.
@@ -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: input.nativeAstMetadata
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.1",
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-kernel": "0.3.0",
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-rust": "0.2.0",
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-c": "0.2.0"
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
  }