@shapeshift-labs/frontier-lang-compiler 0.2.54 → 0.2.55

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 CHANGED
@@ -19,6 +19,19 @@ const result = compileFrontierSource(source, { target: 'typescript' });
19
19
  if (result.ok) console.log(result.output);
20
20
  ```
21
21
 
22
+ Run a small end-to-end demo after installing or building the package:
23
+
24
+ ```sh
25
+ npm run build
26
+ node examples/native-js-to-rust-demo.mjs
27
+ ```
28
+
29
+ The demo prints JavaScript source, the Frontier universal AST/semantic-index summary,
30
+ Rust declaration stubs, a host-adapter Rust projection, and a direct Frontier-source
31
+ to Rust projection. Native JavaScript projection remains loss-aware: without a
32
+ target adapter the compiler emits review-required stubs rather than claiming a
33
+ lossless JS-to-Rust transpilation.
34
+
22
35
  Emit code with declaration-level source-map sidecars for semantic review and merge admission:
23
36
 
24
37
  ```js
@@ -18,9 +18,13 @@ export function createSemanticImportSidecar(importResult, options = {}) {
18
18
  const proofSpec = summarizeSemanticImportSidecarProofSpec(importEntries);
19
19
  const paradigmSemantics = summarizeSemanticImportSidecarParadigmSemantics(importEntries);
20
20
  const dependencies = summarizeSemanticImportDependencies(imports);
21
+ const entryReadiness = importEntries.reduce(
22
+ (current, entry) => maxSemanticMergeReadiness(current, entry.readiness),
23
+ lossSummary.semanticMergeReadiness
24
+ );
21
25
  const readiness = mergeCandidates.reduce(
22
26
  (current, candidate) => maxSemanticMergeReadiness(current, candidate.readiness),
23
- lossSummary.semanticMergeReadiness
27
+ entryReadiness
24
28
  );
25
29
  const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
26
30
  const quality = createSemanticImportSidecarQuality({
@@ -1,24 +1,32 @@
1
- import{declarationRecord}from'./declarationRecord.js';import{nativeNodeId}from'./nativeNodeId.js';import{shortNodeText}from'./shortNodeText.js';import{treeSitterFieldText}from'./treeSitterFieldText.js';
1
+ import{declarationRecord}from'./declarationRecord.js';import{shortNodeText}from'./shortNodeText.js';import{treeSitterChildText,treeSitterFieldText}from'./treeSitterNodeAccess.js';
2
2
  export function treeSitterDeclaration(node, kind, nativeNodeId, input) {
3
3
  if (/import|include|use/.test(kind)) {
4
- const name = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source') ?? shortNodeText(node);
4
+ const name = treeSitterNamedText(node, ['string', 'string_fragment', 'identifier']) ?? shortNodeText(node);
5
5
  if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
6
6
  }
7
7
  if (/function|method|fn_item|function_declaration/.test(kind)) {
8
- const name = treeSitterFieldText(node, 'name');
8
+ const name = treeSitterNamedText(node, ['identifier', 'property_identifier']);
9
9
  if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
10
10
  }
11
11
  if (/class/.test(kind)) {
12
- const name = treeSitterFieldText(node, 'name');
12
+ const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
13
13
  if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
14
14
  }
15
15
  if (/interface/.test(kind)) {
16
- const name = treeSitterFieldText(node, 'name');
16
+ const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
17
17
  if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
18
18
  }
19
19
  if (/struct|enum|type/.test(kind)) {
20
- const name = treeSitterFieldText(node, 'name');
20
+ const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
21
21
  if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
22
22
  }
23
+ if (/variable_declarator|property_declaration|field_declaration/.test(kind)) {
24
+ const name = treeSitterNamedText(node, ['identifier', 'property_identifier', 'field_identifier']);
25
+ if (name) return declarationRecord(input, nativeNodeId, name, 'variable', 'definition');
26
+ }
23
27
  return undefined;
24
28
  }
29
+
30
+ function treeSitterNamedText(node, childKinds) {
31
+ return treeSitterFieldText(node, 'name') ?? treeSitterFieldText(node, 'declarator') ?? treeSitterChildText(node, childKinds);
32
+ }
@@ -1,5 +1 @@
1
- import{shortNodeText}from'./shortNodeText.js';
2
- export function treeSitterFieldText(node, field) {
3
- if (typeof node.childForFieldName !== 'function') return undefined;
4
- return shortNodeText(node.childForFieldName(field));
5
- }
1
+ export{treeSitterFieldText}from'./treeSitterNodeAccess.js';
@@ -0,0 +1,75 @@
1
+ import{shortNodeText}from'./shortNodeText.js';
2
+ export function treeSitterNodeKind(node) {
3
+ return String(node?.type ?? node?.kind ?? 'node');
4
+ }
5
+
6
+ export function treeSitterChildren(node) {
7
+ const children = treeSitterArrayChildren(node, 'children');
8
+ if (children.length) return children;
9
+ const childCountChildren = treeSitterIndexedChildren(node, 'childCount', 'child');
10
+ if (childCountChildren.length) return childCountChildren;
11
+ const namedChildren = treeSitterArrayChildren(node, 'namedChildren');
12
+ if (namedChildren.length) return namedChildren;
13
+ return treeSitterIndexedChildren(node, 'namedChildCount', 'namedChild');
14
+ }
15
+
16
+ export function treeSitterChildText(node, kinds) {
17
+ const wanted = new Set(kinds);
18
+ const stack = [...treeSitterChildren(node)];
19
+ while (stack.length) {
20
+ const child = stack.shift();
21
+ if (!child || typeof child !== 'object') continue;
22
+ if (wanted.has(treeSitterNodeKind(child))) {
23
+ const text = shortNodeText(child);
24
+ if (text) return text;
25
+ }
26
+ stack.unshift(...treeSitterChildren(child));
27
+ }
28
+ return undefined;
29
+ }
30
+
31
+ export function treeSitterFieldText(node, field) {
32
+ const exact = treeSitterFieldNode(node, field);
33
+ if (exact) return shortNodeText(exact);
34
+ return treeSitterNamedFieldText(node, field);
35
+ }
36
+
37
+ function treeSitterFieldNode(node, field) {
38
+ if (typeof node?.childForFieldName === 'function') return node.childForFieldName(field);
39
+ if (typeof node?.child_by_field_name === 'function') return node.child_by_field_name(field);
40
+ return undefined;
41
+ }
42
+
43
+ function treeSitterNamedFieldText(node, field) {
44
+ for (const child of treeSitterChildren(node)) {
45
+ if (treeSitterFieldName(child) === field) {
46
+ const text = shortNodeText(child);
47
+ if (text) return text;
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+
53
+ function treeSitterFieldName(node) {
54
+ if (typeof node?.fieldName === 'string') return node.fieldName;
55
+ if (typeof node?.field_name === 'string') return node.field_name;
56
+ if (typeof node?.fieldName === 'function') return node.fieldName();
57
+ if (typeof node?.field_name === 'function') return node.field_name();
58
+ return undefined;
59
+ }
60
+
61
+ function treeSitterArrayChildren(node, field) {
62
+ const value = node?.[field];
63
+ return Array.isArray(value) ? value.filter((child) => child && typeof child === 'object') : [];
64
+ }
65
+
66
+ function treeSitterIndexedChildren(node, countField, childMethod) {
67
+ const count = Number(node?.[countField]);
68
+ if (!Number.isFinite(count) || count <= 0 || typeof node?.[childMethod] !== 'function') return [];
69
+ const children = [];
70
+ for (let index = 0; index < count; index += 1) {
71
+ const child = node[childMethod](index);
72
+ if (child && typeof child === 'object') children.push(child);
73
+ }
74
+ return children;
75
+ }
@@ -1,5 +1,5 @@
1
1
  import{idFragment}from'../../native-import-utils.js';
2
- import{nativeNodeId}from'./nativeNodeId.js';import{numberOrUndefined}from'./numberOrUndefined.js';import{shortNodeText}from'./shortNodeText.js';import{spanFromTreeSitterNode}from'./spanFromTreeSitterNode.js';import{treeSitterDeclaration}from'./treeSitterDeclaration.js';
2
+ import{nativeNodeId}from'./nativeNodeId.js';import{numberOrUndefined}from'./numberOrUndefined.js';import{shortNodeText}from'./shortNodeText.js';import{spanFromTreeSitterNode}from'./spanFromTreeSitterNode.js';import{treeSitterDeclaration}from'./treeSitterDeclaration.js';import{treeSitterChildren,treeSitterNodeKind}from'./treeSitterNodeAccess.js';
3
3
  export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
4
4
  if (!node || typeof node !== 'object' || context.truncated) return undefined;
5
5
  if (context.objectIds.has(node)) return context.objectIds.get(node);
@@ -8,7 +8,7 @@ export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
8
8
  return undefined;
9
9
  }
10
10
  context.counter += 1;
11
- const kind = String(node.type ?? node.kind ?? 'node');
11
+ const kind = treeSitterNodeKind(node);
12
12
  const span = spanFromTreeSitterNode(node, context.input);
13
13
  const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
14
14
  context.objectIds.set(node, id);
@@ -82,32 +82,6 @@ export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
82
82
  return id;
83
83
  }
84
84
 
85
- function treeSitterChildren(node) {
86
- const children = treeSitterArrayChildren(node, 'children');
87
- if (children.length) return children;
88
- const childCountChildren = treeSitterIndexedChildren(node, 'childCount', 'child');
89
- if (childCountChildren.length) return childCountChildren;
90
- const namedChildren = treeSitterArrayChildren(node, 'namedChildren');
91
- if (namedChildren.length) return namedChildren;
92
- return treeSitterIndexedChildren(node, 'namedChildCount', 'namedChild');
93
- }
94
-
95
- function treeSitterArrayChildren(node, field) {
96
- const value = node[field];
97
- return Array.isArray(value) ? value.filter((child) => child && typeof child === 'object') : [];
98
- }
99
-
100
- function treeSitterIndexedChildren(node, countField, childMethod) {
101
- const count = Number(node[countField]);
102
- if (!Number.isFinite(count) || count <= 0 || typeof node[childMethod] !== 'function') return [];
103
- const children = [];
104
- for (let index = 0; index < count; index += 1) {
105
- const child = node[childMethod](index);
106
- if (child && typeof child === 'object') children.push(child);
107
- }
108
- return children;
109
- }
110
-
111
85
  function treeSitterBoolean(node, ...fields) {
112
86
  for (const field of fields) {
113
87
  const value = node[field];
@@ -18,6 +18,7 @@ function semanticImportSidecarEntry(imported, index, options) {
18
18
  const proofSpec = summarizeProofSpecLayer(imported?.universalAst?.proof ?? imported?.proof);
19
19
  const paradigmSemantics = summarizeParadigmSemanticsLayer(imported?.universalAst?.paradigmSemantics ?? imported?.paradigmSemantics);
20
20
  const dependencies = summarizeSemanticImportDependencyRelations(semanticIndex?.relations ?? []);
21
+ const readiness = semanticImportEntryReadiness(imported);
21
22
  const mappingsBySymbolId = new Map();
22
23
  for (const mapping of sourceMapMappings) {
23
24
  if (mapping.semanticSymbolId && !mappingsBySymbolId.has(mapping.semanticSymbolId)) {
@@ -44,7 +45,7 @@ function semanticImportSidecarEntry(imported, index, options) {
44
45
  ownershipRegionId: region.id,
45
46
  ownershipKey: region.key,
46
47
  ownershipRegionKind: region.regionKind,
47
- readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
48
+ readiness
48
49
  });
49
50
  }
50
51
  const ownershipRegions = uniqueRecordsById(regions);
@@ -71,7 +72,7 @@ function semanticImportSidecarEntry(imported, index, options) {
71
72
  paradigmSemantics,
72
73
  dependencyRelationCount: dependencies.total,
73
74
  dependencyPredicates: dependencies.predicates,
74
- readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review',
75
+ readiness,
75
76
  emptySemanticIndex: symbols.length === 0,
76
77
  regionTaxonomy,
77
78
  symbols,
@@ -79,4 +80,14 @@ function semanticImportSidecarEntry(imported, index, options) {
79
80
  };
80
81
  }
81
82
 
83
+ function semanticImportEntryReadiness(imported) {
84
+ const readiness = imported?.metadata?.semanticMergeReadiness
85
+ ?? imported?.metadata?.nativeImportLossSummary?.semanticMergeReadiness
86
+ ?? imported?.readiness?.semanticMergeReadiness
87
+ ?? imported?.readiness?.readiness
88
+ ?? (typeof imported?.readiness === 'string' ? imported.readiness : undefined)
89
+ ?? imported?.mergeCandidates?.[0]?.readiness;
90
+ return readiness ?? 'needs-review';
91
+ }
92
+
82
93
  export { semanticImportSidecarEntry };
@@ -0,0 +1,148 @@
1
+ import {
2
+ compileFrontierSource,
3
+ compileNativeSource,
4
+ importNativeSource,
5
+ writeUniversalAstJson
6
+ } from '../dist/index.js';
7
+
8
+ const javascriptSource = `import { nanoid } from "nanoid";
9
+
10
+ export function addTodo(title) {
11
+ return { id: nanoid(), title };
12
+ }
13
+
14
+ export class TodoStore {
15
+ save(title) {
16
+ return addTodo(title);
17
+ }
18
+ }
19
+ `;
20
+
21
+ const imported = importNativeSource({
22
+ language: 'javascript',
23
+ sourcePath: 'src/todo.js',
24
+ sourceText: javascriptSource
25
+ });
26
+
27
+ const graphSummary = {
28
+ universalAst: imported.universalAst.kind,
29
+ semanticIndexId: imported.semanticIndex.id,
30
+ symbols: imported.semanticIndex.symbols.map((symbol) => ({
31
+ name: symbol.name,
32
+ kind: symbol.kind,
33
+ region: symbol.metadata?.ownershipRegionKind
34
+ })),
35
+ sourceMapMappings: imported.sourceMaps[0]?.mappings.length ?? 0,
36
+ mergeReadiness: imported.metadata.nativeImportLossSummary.semanticMergeReadiness,
37
+ losses: imported.losses.map((loss) => loss.kind)
38
+ };
39
+
40
+ console.log('--- JavaScript source ---');
41
+ console.log(javascriptSource.trim());
42
+ console.log('\n--- Frontier graph summary ---');
43
+ console.log(JSON.stringify(graphSummary, null, 2));
44
+ console.log('\n--- Universal AST envelope excerpt ---');
45
+ console.log(JSON.stringify(universalAstExcerpt(imported), null, 2));
46
+
47
+ const stubProjection = compileNativeSource(imported, {
48
+ target: 'rust',
49
+ targetPath: 'src/todo.rs'
50
+ });
51
+
52
+ console.log('\n--- Rust declaration stubs without a target adapter ---');
53
+ console.log(`ok=${stubProjection.ok} readiness=${stubProjection.readiness.readiness} mode=${stubProjection.outputMode}`);
54
+ console.log(stubProjection.output.trim());
55
+
56
+ const adapterProjection = compileNativeSource(imported, {
57
+ target: 'rust',
58
+ targetPath: 'src/todo.rs',
59
+ targetAdapters: [createDemoJsToRustAdapter()]
60
+ });
61
+
62
+ console.log('\n--- Rust output with a host-owned target adapter ---');
63
+ console.log(`ok=${adapterProjection.ok} readiness=${adapterProjection.readiness.readiness} mode=${adapterProjection.outputMode}`);
64
+ console.log(adapterProjection.output.trim());
65
+
66
+ const frontierSource = `module TodoApp @id("mod_todo")
67
+
68
+ type TodoInput @id("type_todo_input") {
69
+ title: Text
70
+ }
71
+
72
+ entity Todo @id("ent_todo") {
73
+ title @id("field_title"): Text
74
+ }
75
+
76
+ action addTodo @id("action_add") {
77
+ input TodoInput
78
+ writes field_title
79
+ returns Patch
80
+ }
81
+ `;
82
+
83
+ const canonicalRust = compileFrontierSource(frontierSource, { target: 'rust' });
84
+
85
+ console.log('\n--- Frontier source projected directly to Rust ---');
86
+ console.log(`ok=${canonicalRust.ok} target=${canonicalRust.target}`);
87
+ console.log(canonicalRust.output.trim());
88
+
89
+ function universalAstExcerpt(importResult) {
90
+ const parsed = JSON.parse(writeUniversalAstJson(importResult.universalAst));
91
+ return {
92
+ kind: parsed.kind,
93
+ id: parsed.id,
94
+ documentCount: parsed.documents?.length ?? 0,
95
+ symbolCount: parsed.semanticIndex?.symbols?.length ?? 0,
96
+ sourceMapCount: parsed.sourceMaps?.length ?? 0
97
+ };
98
+ }
99
+
100
+ function createDemoJsToRustAdapter() {
101
+ return {
102
+ id: 'demo-js-to-rust-target-adapter',
103
+ sourceLanguage: 'javascript',
104
+ target: 'rust',
105
+ version: '0.0.0-demo',
106
+ capabilities: ['declaration-stubs'],
107
+ coverage: {
108
+ readiness: 'ready',
109
+ handledLossKinds: ['opaqueNative', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
110
+ notes: ['Demo adapter turns Frontier semantic symbols into deterministic Rust scaffolding.']
111
+ },
112
+ project(input) {
113
+ const symbols = input.importResult.semanticIndex?.symbols ?? [];
114
+ return {
115
+ output: renderRustScaffold(symbols),
116
+ readiness: 'ready',
117
+ evidence: [{
118
+ id: 'evidence_demo_js_to_rust_adapter',
119
+ kind: 'projection',
120
+ status: 'passed',
121
+ summary: 'Demo adapter projected JavaScript semantic symbols to Rust scaffolding.'
122
+ }]
123
+ };
124
+ }
125
+ };
126
+ }
127
+
128
+ function renderRustScaffold(symbols) {
129
+ const lines = ['// Generated from Frontier semantic graph evidence.'];
130
+ for (const symbol of symbols) {
131
+ if (symbol.kind === 'class') {
132
+ lines.push(`pub struct ${symbol.name};`, '');
133
+ } else if (symbol.kind === 'function' || symbol.kind === 'method') {
134
+ lines.push(`pub fn ${rustName(symbol.name)}() {`);
135
+ lines.push(' todo!("port body from native source evidence");');
136
+ lines.push('}', '');
137
+ }
138
+ }
139
+ return `${lines.join('\n').trim()}\n`;
140
+ }
141
+
142
+ function rustName(name) {
143
+ return String(name)
144
+ .replace(/\./g, '_')
145
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
146
+ .replace(/[^A-Za-z0-9_]/g, '_')
147
+ .toLowerCase();
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.54",
3
+ "version": "0.2.55",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",