@shapeshift-labs/frontier-lang-parser 0.3.32 → 0.3.34

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
@@ -220,6 +220,22 @@ npm install @shapeshift-labs/frontier-lang-parser
220
220
 
221
221
  The parser projects text into `@shapeshift-labs/frontier-lang-kernel` documents. The syntax is intentionally small and experimental.
222
222
 
223
+ ## Source syntax reports
224
+
225
+ Use `inspectFrontierSourceSyntax` when a `.frontier` file is being treated as authored source, not only as convenient input to `parseFrontierSource`. It returns a `frontier.lang.sourceSyntaxReport` with every module-level or top-level declaration block the parser can see, whether that block kind is recognized, and any unsupported block kinds that would make a translation or merge proof fail closed.
226
+
227
+ ```js
228
+ import { inspectFrontierSourceSyntax } from '@shapeshift-labs/frontier-lang-parser';
229
+
230
+ const report = inspectFrontierSourceSyntax(source);
231
+
232
+ if (report.summary.failClosed) {
233
+ console.error(report.summary.unknownKinds);
234
+ }
235
+ ```
236
+
237
+ Nested child syntax such as `render` blocks inside a `view` is not reported as an unknown top-level block; the parent parser owns those child rows. The report is evidence about parser coverage only. It keeps `autoMergeClaim` and `semanticEquivalenceClaim` false.
238
+
223
239
  ## Authored view render graph syntax
224
240
 
225
241
  `.frontier` view blocks can describe UI render graphs directly. Nested `render`
@@ -473,7 +489,7 @@ conversion TodoJavascriptToRust @id("conversion_todo_js_rust") {
473
489
 
474
490
  `sourceRuntime` and `targetRuntime` become runtime maps. `runtimeRequirement` rows become proof obligations for host/runtime capabilities, including authored `requiredSignals` denominators such as source hashes, target hashes, probe ids, runtime commands, telemetry hashes, and capability-specific trace hashes. `proofEvidence` and `evidence` attach evidence ids, but the compiler still requires bound evidence records before a proof obligation is satisfied. `dialect` and `extern` rows preserve dialect-specific constructs, projection readiness, loss/evidence ids, and binding metadata without requiring the authored Frontier file to drop down to raw JSON.
475
491
 
476
- `constraint` rows accept every universal conversion constraint family used by route admission, including hyphenated spellings such as `module-constraint`, `scope-binding`, `memory-model`, `effect-constraint`, `control-flow`, `borrow-scope`, `borrow-checker`, `host-environment`, `callable-boundary`, `adt-pattern`, `data-layout`, `numeric-semantics`, `text-semantics`, `collection-semantics`, `serialization-semantics`, `dependency-semantics`, `object-model`, and `protocol`. The parser preserves family-specific fields such as module specifiers, package conditions, binding/reference ids, memory ordering, locks, capabilities, host permissions, callable signatures, pattern exhaustiveness, ABI/layout hints, numeric/text/collection behavior, wire formats, dependency lockfile evidence, and effect adapters as authored evidence inputs. Record-level targets use explicit labels such as `effectTarget` so `role target` rows cannot be mistaken for an authored target field. These rows do not prove translation equivalence; they make the required proof surface explicit for downstream gates and admission records.
492
+ `constraint` rows accept every universal conversion constraint family used by route admission, including hyphenated spellings such as `module-constraint`, `scope-binding`, `memory-model`, `effect-constraint`, `control-flow`, `borrow-scope`, `borrow-checker`, `host-environment`, `callable-boundary`, `adt-pattern`, `data-layout`, `layout-style`, `layout`, `style`, `numeric-semantics`, `text-semantics`, `collection-semantics`, `serialization-semantics`, `dependency-semantics`, `object-model`, and `protocol`. The parser preserves family-specific fields such as module specifiers, package conditions, binding/reference ids, memory ordering, locks, capabilities, host permissions, callable signatures, pattern exhaustiveness, ABI/layout hints, selector/style/cascade/layout evidence, numeric/text/collection behavior, wire formats, dependency lockfile evidence, and effect adapters as authored evidence inputs. Constraint entries and family-specific records can also carry `sourceMap`, `sourceMapMapping`, `proofObligation`, `proofEvidence`, `missingEvidence`, and `failClosed` so downstream compiler, merge, and admission tooling can keep source-bound denominators visible. Record-level targets use explicit labels such as `effectTarget` so `role target` rows cannot be mistaken for an authored target field. These rows do not prove translation equivalence; they make the required proof surface explicit for downstream gates and admission records.
477
493
 
478
494
  ## Authored runtime capability syntax
479
495
 
@@ -8,6 +8,7 @@ const FAMILY_ROWS = [
8
8
  ['callableBoundary', 'callableBoundaryConstraints', 'sourceCallables', 'targetCallables', ['callableBoundaryConstraint', 'callable-boundary', 'callable-boundary-constraint'], { extraSourceKeys: ['sourceCallableBoundaryRecords'], extraTargetKeys: ['targetCallableBoundaryRecords'] }],
9
9
  ['adtPattern', 'adtPatternConstraints', 'sourcePatterns', 'targetPatterns', ['adtPatternConstraint', 'adt-pattern', 'adt-pattern-constraint'], { extraSourceKeys: ['sourceAdtPatternRecords'], extraTargetKeys: ['targetAdtPatternRecords'] }],
10
10
  ['dataLayout', 'dataLayoutConstraints', 'sourceLayouts', 'targetLayouts', ['dataLayoutConstraint', 'data-layout', 'data-layout-constraint'], { extraSourceKeys: ['sourceDataLayoutRecords'], extraTargetKeys: ['targetDataLayoutRecords'] }],
11
+ ['layoutStyle', 'layoutStyleConstraints', 'sourceLayoutStyles', 'targetLayoutStyles', ['layoutStyleConstraint', 'layout-style', 'layout-style-constraint', 'layout', 'layoutConstraint', 'layout-constraint', 'style', 'styleConstraint', 'style-constraint', 'css-style', 'css-rule', 'style-layout', 'render-layout'], { extraSourceKeys: ['sourceLayoutStyleRecords'], extraTargetKeys: ['targetLayoutStyleRecords'] }],
11
12
  ['effect', 'effectConstraints', 'sourceEffects', 'targetEffects', ['effectConstraint', 'effect-constraint']],
12
13
  ['concurrencyModel', 'concurrencyModelConstraints', 'sourceConcurrencyModels', 'targetConcurrencyModels', ['concurrencyModelConstraint', 'concurrency-model', 'concurrency-model-constraint'], { extraSourceKeys: ['sourceConcurrencyModelRecords'], extraTargetKeys: ['targetConcurrencyModelRecords'] }],
13
14
  ['errorModel', 'errorModelConstraints', 'sourceErrors', 'targetErrors', ['errorModelConstraint', 'error-model', 'error-model-constraint'], { extraSourceKeys: ['sourceErrorModelRecords'], extraTargetKeys: ['targetErrorModelRecords'] }],
@@ -48,7 +49,7 @@ const WORD_FIELDS = [
48
49
  ['controlFlowKind'], ['sourceControlFlowId'], ['sourceId', 'from', 'sourceId'], ['targetId', 'to', 'targetId'], ['label'],
49
50
  ['conditionHash'], ['orderingKey', 'orderingKey', 'orderKey'], ['lifetimeKind'], ['lifetimeRegionId', 'lifetimeRegion', 'lifetimeRegionId'],
50
51
  ['regionKind'], ['resourceId', 'resource', 'resourceId'],
51
- ['functionName'], ['methodName'], ['callableName'], ['callableKind'], ['callSignatureHash'],
52
+ ['functionName'], ['methodName'], ['callableName'], ['callableKind'], ['callSignatureHash'], ['restParameter', 'restParameter', 'rest'],
52
53
  ['receiverKind', 'receiverKind', 'receiver'], ['thisBinding'], ['selfBinding'], ['returnKind', 'returnKind', 'returnType'],
53
54
  ['asyncKind', 'asyncKind', 'asyncMode'], ['generatorKind', 'generatorKind', 'yieldKind'], ['callbackKind'],
54
55
  ['closureCapture', 'closureCapture', 'captureKind'], ['overloadSet', 'overloadSet', 'overloads'],
@@ -57,7 +58,7 @@ const WORD_FIELDS = [
57
58
  ['symbolKind'], ['relationKind'], ['representationKind'], ['typeId', 'typeId', 'nodeId'], ['nodeId'], ['structId'], ['unionId'],
58
59
  ['enumId'], ['fieldId'], ['bitfieldId'], ['endianness', 'endianness', 'endian'], ['repr', 'repr', 'reprAttribute'],
59
60
  ['numericKind', 'numericKind', 'numberKind'], ['numberKind'], ['numericTypeName'], ['signedness'], ['overflowMode', 'overflowMode', 'overflowBehavior'],
60
- ['divisionMode', 'divisionMode', 'integerDivisionMode'], ['moduloMode', 'moduloMode', 'remainderMode'], ['floatPrecision'], ['roundingMode'],
61
+ ['divisionMode', 'divisionMode', 'integerDivisionMode'], ['moduloMode', 'moduloMode', 'remainderMode'], ['floatFormat'], ['floatPrecision'], ['roundingMode'],
61
62
  ['separatorPolicy'], ['coercionKind', 'coercionKind', 'conversionKind'], ['conversionKind'], ['literalKind'],
62
63
  ['textKind', 'textKind', 'stringKind'], ['stringKind'], ['stringTypeName'], ['encoding', 'encoding', 'charset', 'codepage'],
63
64
  ['charset'], ['codepage'], ['codeUnit', 'codeUnit', 'codeUnitWidth'], ['indexingUnit', 'indexingUnit', 'indexUnit'],
@@ -83,15 +84,19 @@ const WORD_FIELDS = [
83
84
  ['buildTool', 'buildTool', 'builder'], ['packageManagerVersion', 'packageManagerVersion', 'managerVersion'],
84
85
  ['offlineCache', 'offlineCache', 'cachePolicy'], ['dedupeHoist', 'dedupeHoist', 'hoistPolicy'], ['provenance', 'provenance', 'sourceProvenance'],
85
86
  ['trust', 'trust', 'supplyChainTrust'], ['concurrencyKind'], ['constructId', 'constructId', 'taskId', 'threadId', 'actorId', 'channelId'],
86
- ['taskId'], ['threadId'], ['executor', 'executor', 'queue', 'runtime'], ['executorId'], ['isolationKey'], ['cancellationKey', 'cancellationKey', 'signalId', 'contextId'],
87
+ ['taskId'], ['threadId'], ['scheduler'], ['executor', 'executor', 'queue', 'runtime'], ['executorId'], ['isolationKey'], ['cancellationKey', 'cancellationKey', 'signalId', 'contextId'],
87
88
  ['signalId'], ['contextId'], ['errorKind'], ['errorType', 'errorType', 'exceptionType', 'resultType'], ['exceptionType'], ['resultType'],
88
89
  ['boundaryId', 'boundaryId', 'catchId', 'handlerId'], ['catchId'], ['handlerId'], ['evaluationKind', 'evaluationKind', 'expressionKind'],
89
90
  ['expressionKind'], ['expressionId', 'expressionId', 'nodeId'], ['operator'], ['evaluationOrder', 'evaluationOrder', 'order'],
90
91
  ['expansionKind', 'expansionKind', 'macroKind', 'templateKind', 'decoratorKind', 'generatorKind'], ['macroKind'], ['templateKind'],
91
92
  ['decoratorKind'], ['expansionId'], ['generatorId'], ['generatedSourcePath'], ['expandedHash', 'expandedHash', 'generatedHash'],
92
- ['generatedHash'], ['classId'], ['classKind'], ['prototypeId'], ['mixinId'], ['constructorId'], ['inheritanceKind'], ['referenceSemantics'],
93
+ ['generatedHash'], ['classId'], ['classKind'], ['prototypeId'], ['mixinId'], ['methodId'], ['constructorId'], ['inheritanceKind'], ['referenceSemantics'],
93
94
  ['valueSemantics'], ['protocolKind'], ['protocolName'], ['traitName'], ['interfaceName'], ['subjectName', 'subjectName', 'receiverName', 'implementedFor'],
94
- ['implementedFor'], ['sourcePath', 'sourcePath', 'path'], ['sourceHash'], ['target', 'effectTarget', 'targetResource']
95
+ ['implementedFor'], ['sourcePath', 'sourcePath', 'path'], ['sourceHash'], ['selector'], ['value'], ['styleProperty', 'styleProperty', 'property', 'cssProperty'],
96
+ ['cssProperty', 'cssProperty', 'styleProperty', 'property'], ['computedValue'], ['cascadeLayer'], ['specificity'], ['mediaQuery'],
97
+ ['containerQuery'], ['boxModel'], ['display'], ['position'], ['zIndex'], ['writingMode'], ['direction'], ['viewport'], ['renderTreeId'],
98
+ ['styleRuleId'], ['computedStyleHash'], ['layoutSnapshotHash'], ['bitmapHash'], ['accessibilityTreeHash'], ['focusOrderHash'],
99
+ ['target', 'effectTarget', 'targetResource']
95
100
  ];
96
101
  const LIST_FIELDS = [
97
102
  ['factKinds', 'fact', 'facts', 'factKind', 'factKinds'], ['importAttributes', 'importAttribute', 'importAttributes', 'assertion', 'assertions'],
@@ -108,14 +113,20 @@ const LIST_FIELDS = [
108
113
  ['devDependencies'], ['features', 'features', 'extras', 'flags'], ['lifecycleScripts', 'lifecycleScripts', 'scripts'],
109
114
  ['requirementNames', 'requirementNames', 'requirements', 'methods', 'members'], ['associatedTypeNames', 'associatedTypeNames', 'associatedTypes'],
110
115
  ['boundNames', 'boundNames', 'bounds', 'traitBounds', 'protocolBounds', 'whereBounds'], ['implementationKinds', 'implementationKinds', 'implKinds', 'implementations'],
111
- ['dispatchKinds', 'dispatchKinds', 'dispatchModes'], ['coherenceKinds', 'coherenceKinds', 'coherenceRules'], ['evidenceIds', 'evidence', 'evidenceIds']
116
+ ['dispatchKinds', 'dispatchKinds', 'dispatchModes'], ['coherenceKinds', 'coherenceKinds', 'coherenceRules'],
117
+ ['sourceMapIds', 'sourceMap', 'sourceMaps', 'sourceMapId', 'sourceMapIds'],
118
+ ['sourceMapMappingIds', 'sourceMapMapping', 'sourceMapMappings', 'sourceMapMappingId', 'sourceMapMappingIds'],
119
+ ['proofObligationIds', 'proofObligation', 'proofObligations', 'proofObligationId', 'proofObligationIds', 'obligation', 'obligations'],
120
+ ['proofEvidenceIds', 'proofEvidence', 'proofEvidenceId', 'proofEvidenceIds'],
121
+ ['missingEvidence', 'missingEvidence', 'missingEvidenceIds'],
122
+ ['evidenceIds', 'evidence', 'evidenceIds']
112
123
  ];
113
124
  const NUMBER_FIELDS = [
114
125
  ['arity'], ['size'], ['alignment'], ['parameterCount'], ['requiredParameterCount'], ['optionalParameterCount'],
115
126
  ['sizeBytes'], ['alignmentBytes'], ['offsetBytes'], ['pointerWidth'], ['integerWidth'], ['bitWidth'], ['width'],
116
127
  ['codeUnitWidth'], ['indexBase']
117
128
  ];
118
- const FLAG_FIELDS = ['nullable', 'optional', 'publicContract', 'closure', 'captured', 'writeExpr', 'mutable', 'shadowed', 'hoisted', 'typeOnly', 'isTypeReference', 'isValueReference', 'shared', 'volatile', 'atomic', 'adapterRequired', 'async', 'generator', 'exceptional', 'cancellable', 'variadic', 'signed', 'nan', 'infinity', 'deterministic', 'streaming', 'framing', 'copyOnWrite', 'multipleInheritance', 'reflection', 'staticDispatch', 'virtual', 'spawn', 'await', 'structured', 'reentrant', 'cancelable'];
129
+ const FLAG_FIELDS = ['nullable', 'optional', 'publicContract', 'closure', 'captured', 'writeExpr', 'mutable', 'shadowed', 'hoisted', 'typeOnly', 'isTypeReference', 'isValueReference', 'shared', 'volatile', 'atomic', 'adapterRequired', 'async', 'generator', 'exceptional', 'cancellable', 'variadic', 'signed', 'nan', 'infinity', 'deterministic', 'streaming', 'framing', 'copyOnWrite', 'multipleInheritance', 'reflection', 'staticDispatch', 'virtual', 'spawn', 'await', 'structured', 'reentrant', 'cancelable', 'failClosed'];
119
130
 
120
131
  export const FAMILIES = Object.freeze(Object.fromEntries(FAMILY_ROWS.flatMap(([name, field, sourceKey, targetKey, aliases = [], extra = {}]) => {
121
132
  const config = { field, sourceKey, targetKey, ...extra };
@@ -88,10 +88,15 @@ function addConstraint(plan, family, name, text) {
88
88
  sourceLanguage: readInlineWord('sourceLanguage', text) ?? plan.sourceLanguage,
89
89
  target: readInlineWord('targetLanguage', text) ?? plan.targets[0],
90
90
  mode: readInlineWord('mode', text),
91
+ sourceMapIds: readInlineList(text, 'sourceMap', 'sourceMaps', 'sourceMapId', 'sourceMapIds'),
92
+ sourceMapMappingIds: readInlineList(text, 'sourceMapMapping', 'sourceMapMappings', 'sourceMapMappingId', 'sourceMapMappingIds'),
93
+ proofObligationIds: readInlineList(text, 'proofObligation', 'proofObligations', 'proofObligationId', 'proofObligationIds', 'obligation', 'obligations'),
94
+ proofEvidenceIds: readInlineList(text, 'proofEvidence', 'proofEvidenceId', 'proofEvidenceIds'),
91
95
  evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
92
96
  missingEvidence: readInlineList(text, 'missingEvidence'),
93
97
  blockers: readInlineList(text, 'blocker', 'blockers'),
94
98
  review: readInlineList(text, 'review'),
99
+ failClosed: readInlineFlag('failClosed', text),
95
100
  metadata: { name, family, authoredConversionBlockId: plan.id }
96
101
  });
97
102
  const recordKey = role === 'target' ? config.targetKey : config.sourceKey;
@@ -113,6 +118,12 @@ function resourceGraphFromRecord(record, entry) {
113
118
  sourcePath: record.sourcePath,
114
119
  sourceHash: record.sourceHash,
115
120
  evidenceIds,
121
+ sourceMapIds: record.sourceMapIds ?? entry.sourceMapIds,
122
+ sourceMapMappingIds: record.sourceMapMappingIds ?? entry.sourceMapMappingIds,
123
+ proofObligationIds: record.proofObligationIds ?? entry.proofObligationIds,
124
+ proofEvidenceIds: record.proofEvidenceIds ?? entry.proofEvidenceIds,
125
+ missingEvidence: record.missingEvidence ?? entry.missingEvidence,
126
+ failClosed: record.failClosed ?? entry.failClosed,
116
127
  resources: [{
117
128
  id: resourceId,
118
129
  resourceKind: record.resourceKind ?? record.kind ?? record.constraintKind,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,47 @@
1
1
  import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  export interface ParseFrontierOptions { readonly id?: string; readonly name?: string; }
3
+ export declare const FrontierSourceBlockKinds: readonly string[];
4
+ export interface FrontierSourceBlockSyntaxRecord {
5
+ readonly kind: string;
6
+ readonly name: string;
7
+ readonly id?: string;
8
+ readonly header: string;
9
+ readonly startOffset: number;
10
+ readonly endOffset: number;
11
+ readonly bodyStartOffset: number;
12
+ readonly bodyEndOffset: number;
13
+ readonly location: { readonly line: number; readonly column: number; readonly offset: number };
14
+ readonly moduleId?: string;
15
+ readonly moduleName?: string;
16
+ readonly recognized: boolean;
17
+ }
18
+ export interface FrontierUnknownSourceBlockSyntaxRecord extends FrontierSourceBlockSyntaxRecord {
19
+ readonly recognized: false;
20
+ readonly reason: 'unsupported-top-level-block';
21
+ }
22
+ export interface FrontierSourceSyntaxReport {
23
+ readonly kind: 'frontier.lang.sourceSyntaxReport';
24
+ readonly version: 1;
25
+ readonly documentId: string;
26
+ readonly documentName: string;
27
+ readonly blocks: readonly FrontierSourceBlockSyntaxRecord[];
28
+ readonly recognizedBlocks: readonly FrontierSourceBlockSyntaxRecord[];
29
+ readonly unknownBlocks: readonly FrontierUnknownSourceBlockSyntaxRecord[];
30
+ readonly summary: {
31
+ readonly blockCount: number;
32
+ readonly recognizedBlockCount: number;
33
+ readonly unknownBlockCount: number;
34
+ readonly recognizedKinds: readonly string[];
35
+ readonly unknownKinds: readonly string[];
36
+ readonly failClosed: boolean;
37
+ readonly unsupportedSyntax: boolean;
38
+ };
39
+ readonly metadata: {
40
+ readonly sourceBytes: number;
41
+ readonly autoMergeClaim: false;
42
+ readonly semanticEquivalenceClaim: false;
43
+ };
44
+ }
45
+ export declare function inspectFrontierSourceSyntax(source: string, options?: ParseFrontierOptions): FrontierSourceSyntaxReport;
3
46
  export declare function parseFrontierSource(source: string, options?: ParseFrontierOptions): FrontierLangDocument;
4
47
  export declare function parseFrontierFile(name: string, source: string): FrontierLangDocument;
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ import { parseRuntimeCapabilityBlock } from './runtime-capability.js';
16
16
  import { parseNativeSourceBlock } from './source-evidence.js';
17
17
  import { parseTargetProjectionMetadata } from './target-projection.js';
18
18
  import { parseViewBlock } from './view.js';
19
+ import { FrontierSourceBlockKinds } from './source-syntax-report.js';
20
+ export { FrontierSourceBlockKinds, inspectFrontierSourceSyntax } from './source-syntax-report.js';
19
21
 
20
22
  export function parseFrontierSource(source, options = {}) {
21
23
  const nodes = [];
@@ -76,7 +78,7 @@ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[
76
78
  function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
77
79
  function readBlocks(source) {
78
80
  const blocks = [];
79
- const header = /\b(entity|state|action|view|migration|capability|effect|type|extern|lattice|nativeSource|target|proof|paradigm|paradigmSemantics|operations|semanticOperations|conversion|universalConversionPlan|constraintSpace|possibilitySpace|decisionGraph|admissionGraph|dialectRegistry|universalDialectRegistry|interlingua|universalInterlingua|resourceGraph|semanticResourceGraph|packageManifest|packageGraph|packageSurface|canvasSurface|canvasGraph|applicationSurface|appHost|plugin|pluginSurface|pluginContract|runtimeCapabilities|runtimeCapabilityMatrix|runtimeHosts)\s+([^{}]+)\{/g;
81
+ const header = new RegExp('\\b(' + FrontierSourceBlockKinds.join('|') + ')\\s+([^{}]+)\\{', 'g');
80
82
  let match;
81
83
  while ((match = header.exec(source))) {
82
84
  let depth = 1; let index = header.lastIndex;
@@ -0,0 +1,170 @@
1
+ export const FrontierSourceBlockKinds = Object.freeze([
2
+ 'entity',
3
+ 'state',
4
+ 'action',
5
+ 'view',
6
+ 'migration',
7
+ 'capability',
8
+ 'effect',
9
+ 'type',
10
+ 'extern',
11
+ 'lattice',
12
+ 'nativeSource',
13
+ 'target',
14
+ 'proof',
15
+ 'paradigm',
16
+ 'paradigmSemantics',
17
+ 'operations',
18
+ 'semanticOperations',
19
+ 'conversion',
20
+ 'universalConversionPlan',
21
+ 'constraintSpace',
22
+ 'possibilitySpace',
23
+ 'decisionGraph',
24
+ 'admissionGraph',
25
+ 'dialectRegistry',
26
+ 'universalDialectRegistry',
27
+ 'interlingua',
28
+ 'universalInterlingua',
29
+ 'resourceGraph',
30
+ 'semanticResourceGraph',
31
+ 'packageManifest',
32
+ 'packageGraph',
33
+ 'packageSurface',
34
+ 'canvasSurface',
35
+ 'canvasGraph',
36
+ 'applicationSurface',
37
+ 'appHost',
38
+ 'plugin',
39
+ 'pluginSurface',
40
+ 'pluginContract',
41
+ 'runtimeCapabilities',
42
+ 'runtimeCapabilityMatrix',
43
+ 'runtimeHosts'
44
+ ]);
45
+
46
+ const FrontierSourceBlockKindSet = new Set(FrontierSourceBlockKinds);
47
+
48
+ export function inspectFrontierSourceSyntax(source, options = {}) {
49
+ const documentId = options.id ?? readId(source) ?? 'mod_frontier';
50
+ const documentName = options.name ?? readName(source) ?? 'FrontierModule';
51
+ const blocks = readCandidateDeclarationBlocks(source).map((block) => ({
52
+ ...block,
53
+ recognized: FrontierSourceBlockKindSet.has(block.kind)
54
+ }));
55
+ const recognizedBlocks = blocks.filter((block) => block.recognized);
56
+ const unknownBlocks = blocks.filter((block) => !block.recognized).map((block) => ({
57
+ ...block,
58
+ reason: 'unsupported-top-level-block'
59
+ }));
60
+ return {
61
+ kind: 'frontier.lang.sourceSyntaxReport',
62
+ version: 1,
63
+ documentId,
64
+ documentName,
65
+ blocks,
66
+ recognizedBlocks,
67
+ unknownBlocks,
68
+ summary: {
69
+ blockCount: blocks.length,
70
+ recognizedBlockCount: recognizedBlocks.length,
71
+ unknownBlockCount: unknownBlocks.length,
72
+ recognizedKinds: unique(recognizedBlocks.map((block) => block.kind)),
73
+ unknownKinds: unique(unknownBlocks.map((block) => block.kind)),
74
+ failClosed: unknownBlocks.length > 0,
75
+ unsupportedSyntax: unknownBlocks.length > 0
76
+ },
77
+ metadata: {
78
+ sourceBytes: source.length,
79
+ autoMergeClaim: false,
80
+ semanticEquivalenceClaim: false
81
+ }
82
+ };
83
+ }
84
+
85
+ function readCandidateDeclarationBlocks(source) {
86
+ const moduleRanges = readModuleRanges(source);
87
+ const blocks = [];
88
+ const header = /(^|\n)\s*([A-Za-z_$][\w$]*)\s+([^{}\n]+)\{/g;
89
+ let match;
90
+ while ((match = header.exec(source))) {
91
+ const fullStart = match.index + match[1].length;
92
+ const leading = /^\s*/.exec(source.slice(fullStart))?.[0].length ?? 0;
93
+ const start = fullStart + leading;
94
+ const kind = match[2];
95
+ if (kind === 'module') continue;
96
+ const open = header.lastIndex - 1;
97
+ const close = findMatchingBrace(source, open);
98
+ const depth = braceDepthBefore(source, start);
99
+ const moduleRange = moduleRanges.find((range) => start > range.open && start < range.close);
100
+ const declarationDepth = moduleRange ? moduleRange.depth + 1 : 0;
101
+ if (depth !== declarationDepth) continue;
102
+ const headerText = match[3].trim();
103
+ blocks.push({
104
+ kind,
105
+ name: nameFrom(headerText),
106
+ id: idFrom(headerText),
107
+ header: headerText,
108
+ startOffset: start,
109
+ endOffset: close + 1,
110
+ bodyStartOffset: open + 1,
111
+ bodyEndOffset: close,
112
+ location: sourcePosition(source, start),
113
+ moduleId: moduleRange?.id,
114
+ moduleName: moduleRange?.name
115
+ });
116
+ }
117
+ return blocks;
118
+ }
119
+
120
+ function readModuleRanges(source) {
121
+ const ranges = [];
122
+ const header = /(^|\n)\s*module\s+([^{}\n]+)\{/g;
123
+ let match;
124
+ while ((match = header.exec(source))) {
125
+ const fullStart = match.index + match[1].length;
126
+ const leading = /^\s*/.exec(source.slice(fullStart))?.[0].length ?? 0;
127
+ const start = fullStart + leading;
128
+ const open = header.lastIndex - 1;
129
+ const close = findMatchingBrace(source, open);
130
+ ranges.push({
131
+ start,
132
+ open,
133
+ close,
134
+ depth: braceDepthBefore(source, start),
135
+ name: nameFrom(match[2].trim()),
136
+ id: idFrom(match[2].trim())
137
+ });
138
+ }
139
+ return ranges;
140
+ }
141
+
142
+ function findMatchingBrace(source, open) {
143
+ let depth = 1;
144
+ for (let index = open + 1; index < source.length; index++) {
145
+ if (source[index] === '{') depth++;
146
+ if (source[index] === '}') depth--;
147
+ if (depth === 0) return index;
148
+ }
149
+ return source.length - 1;
150
+ }
151
+
152
+ function braceDepthBefore(source, offset) {
153
+ let depth = 0;
154
+ for (let index = 0; index < offset; index++) {
155
+ if (source[index] === '{') depth++;
156
+ if (source[index] === '}') depth = Math.max(0, depth - 1);
157
+ }
158
+ return depth;
159
+ }
160
+
161
+ function sourcePosition(source, offset) {
162
+ const lines = source.slice(0, offset).split('\n');
163
+ return { line: lines.length, column: lines[lines.length - 1].length + 1, offset };
164
+ }
165
+
166
+ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[1]; }
167
+ function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
168
+ function idFrom(header) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(header)?.[1]; }
169
+ function nameFrom(header) { return /^([A-Za-z_$][\w$]*)/.exec(header)?.[1] ?? 'Unnamed'; }
170
+ function unique(values) { return [...new Set(values.filter(Boolean))]; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.32",
3
+ "version": "0.3.34",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",