@shapeshift-labs/frontier-lang-parser 0.3.24 → 0.3.26

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,46 @@ 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
+ ## Authored view render graph syntax
224
+
225
+ `.frontier` view blocks can describe UI render graphs directly. Nested `render`
226
+ blocks are flattened into `view.renders`, and each parent stores stable child
227
+ render IDs in `children`. This keeps authored UI structural and target-neutral:
228
+ HTML, JSX, SwiftUI, or another target can lower the same semantic graph without
229
+ the parser claiming browser or runtime equivalence.
230
+
231
+ ```frontier
232
+ view TodoList @id("view_todo_list") {
233
+ reads TodoDb.todos
234
+ dispatches action_add
235
+ prop disabled @id("view_prop_disabled"): Boolean
236
+ event save @id("view_event_save") action action_add input TodoInput
237
+
238
+ render Article @id("render_todo_root") {
239
+ key todo-list-root
240
+
241
+ render Button @id("render_save_button") {
242
+ identity save
243
+ text "Save"
244
+ prop disabled disabled
245
+ on press save
246
+ }
247
+
248
+ render SaveIcon kind component @id("render_save_icon") {
249
+ component Icon
250
+ key save-icon
251
+ prop name "check"
252
+ }
253
+ }
254
+ }
255
+ ```
256
+
257
+ The root render becomes a graph node whose `children` reference
258
+ `render_save_button` and `render_save_icon` in source order. Child props, text,
259
+ and events stay attached to the child render node instead of leaking onto the
260
+ parent. `kind component` records `component` instead of a literal HTML tag, so
261
+ target adapters can decide how to project it.
262
+
223
263
  ## Authored target projection syntax
224
264
 
225
265
  `.frontier` target blocks can carry projection contracts next to their emit settings. These rows describe what a target lowering claims to represent, what it still needs proof for, and which losses or missing evidence must stay visible to merge and translation tooling.
@@ -323,11 +363,18 @@ conversion TodoJavascriptToRust @id("conversion_todo_js_rust") {
323
363
  dialect nodeProcess @id("dialect_node_process") language javascript dialect node.runtime kind runtime target rust disposition unsupported readiness blocked loss loss_node_process_projection
324
364
  extern viteRoutes @id("extern_vite_routes") language javascript dialect vite.plugin.virtual-module externKind generatorArtifact target rust disposition runtime-required evidence evidence_vite_routes_manifest bindingSymbol virtual:routes
325
365
  constraint type publicApi @id("type_constraint_public_api") role source kind public-function symbol symbol:addTodo signatureHash sig_add_todo evidence artifact_todo_title_probe
366
+ constraint module-constraint todoModule @id("module_constraint_todo") role source kind module-boundary specifier ./todo exportedName addTodo packageName @app/todo packageCondition import resolutionKind node16 evidence artifact_todo_title_probe
367
+ constraint scope-binding todoLocal @id("scope_binding_todo") role source kind lexical-binding bindingId binding:todo referenceId ref:todo scopeId scope:handler resolvedBindingId binding:todo evidence artifact_todo_title_probe
368
+ constraint memory-model todoMemory @id("memory_model_todo") role source kind stable-reference resource TodoDb.todos memoryKind shared-memory memoryOrder acquire lockId lock:todo shared evidence artifact_todo_title_probe
369
+ constraint effect-constraint todoWrite @id("effect_constraint_todo_write") role source kind storage-write capability storage.write resource TodoDb.todos adapterRequired evidence artifact_todo_title_probe
370
+ constraint host-environment browserFetch @id("host_environment_fetch") role source kind browser-api capability fetch apiName fetch globalName window permission network adapterRequired evidence artifact_todo_title_probe
326
371
  }
327
372
  ```
328
373
 
329
374
  `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.
330
375
 
376
+ `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`, `data-layout`, and `object-model`. The parser preserves family-specific fields such as module specifiers, package conditions, binding/reference ids, memory ordering, locks, capabilities, host permissions, ABI/layout hints, 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.
377
+
331
378
  ## Authored dialect registry syntax
332
379
 
333
380
  `.frontier` files can carry reusable dialect registries with `dialectRegistry` or `universalDialectRegistry` blocks. These blocks describe language-specific constructs that should stay visible during translation instead of being silently collapsed into generic stubs.
@@ -0,0 +1,113 @@
1
+ const FAMILY_ROWS = [
2
+ ['type', 'typeConstraints', 'sourceTypes', 'targetTypes', ['typeConstraint', 'type-constraint']],
3
+ ['resourceTransfer', 'resourceTransfers', 'sourceGraphs', 'targetGraphs', ['resourceTransferConstraint', 'resource-transfer', 'resource-transfer-constraint', 'ownership'], { graph: true }],
4
+ ['controlFlow', 'controlFlowConstraints', 'sourceControlFlows', 'targetControlFlows', ['controlFlowConstraint', 'control-flow', 'control-flow-constraint']],
5
+ ['lifetime', 'lifetimeConstraints', 'sourceLifetimeConstraints', 'targetLifetimeConstraints', ['lifetimeConstraint', 'lifetime-constraint']],
6
+ ['borrowScope', 'borrowScopeConstraints', 'sourceBorrowScopes', 'targetBorrowScopes', ['borrowScopeConstraint', 'borrow-scope', 'borrow-scope-constraint']],
7
+ ['borrowChecker', 'borrowCheckerConstraints', 'sourceBorrowScopes', 'targetBorrowScopes', ['borrowCheckerConstraint', 'borrow-checker', 'borrow-checker-constraint']],
8
+ ['callableBoundary', 'callableBoundaryConstraints', 'sourceCallables', 'targetCallables', ['callableBoundaryConstraint', 'callable-boundary', 'callable-boundary-constraint'], { extraSourceKeys: ['sourceCallableBoundaryRecords'], extraTargetKeys: ['targetCallableBoundaryRecords'] }],
9
+ ['adtPattern', 'adtPatternConstraints', 'sourcePatterns', 'targetPatterns', ['adtPatternConstraint', 'adt-pattern', 'adt-pattern-constraint'], { extraSourceKeys: ['sourceAdtPatternRecords'], extraTargetKeys: ['targetAdtPatternRecords'] }],
10
+ ['dataLayout', 'dataLayoutConstraints', 'sourceLayouts', 'targetLayouts', ['dataLayoutConstraint', 'data-layout', 'data-layout-constraint'], { extraSourceKeys: ['sourceDataLayoutRecords'], extraTargetKeys: ['targetDataLayoutRecords'] }],
11
+ ['effect', 'effectConstraints', 'sourceEffects', 'targetEffects', ['effectConstraint', 'effect-constraint']],
12
+ ['concurrencyModel', 'concurrencyModelConstraints', 'sourceConcurrencyModels', 'targetConcurrencyModels', ['concurrencyModelConstraint', 'concurrency-model', 'concurrency-model-constraint'], { extraSourceKeys: ['sourceConcurrencyModelRecords'], extraTargetKeys: ['targetConcurrencyModelRecords'] }],
13
+ ['errorModel', 'errorModelConstraints', 'sourceErrors', 'targetErrors', ['errorModelConstraint', 'error-model', 'error-model-constraint'], { extraSourceKeys: ['sourceErrorModelRecords'], extraTargetKeys: ['targetErrorModelRecords'] }],
14
+ ['evaluationModel', 'evaluationModelConstraints', 'sourceEvaluations', 'targetEvaluations', ['evaluationModelConstraint', 'evaluation-model', 'evaluation-model-constraint'], { extraSourceKeys: ['sourceEvaluationModelRecords'], extraTargetKeys: ['targetEvaluationModelRecords'] }],
15
+ ['hostEnvironment', 'hostEnvironmentConstraints', 'sourceHosts', 'targetHosts', ['hostEnvironmentConstraint', 'host-environment', 'host-environment-constraint'], { extraSourceKeys: ['sourceHostEnvironmentRecords'], extraTargetKeys: ['targetHostEnvironmentRecords'] }],
16
+ ['memoryModel', 'memoryModelConstraints', 'sourceMemoryModels', 'targetMemoryModels', ['memoryModelConstraint', 'memory-model', 'memory-model-constraint'], { extraSourceKeys: ['sourceMemoryModelRecords'], extraTargetKeys: ['targetMemoryModelRecords'] }],
17
+ ['metaprogramming', 'metaprogrammingConstraints', 'sourceMetaprograms', 'targetMetaprograms', ['metaprogrammingConstraint', 'metaprogramming-constraint'], { extraSourceKeys: ['sourceMetaprogrammingRecords'], extraTargetKeys: ['targetMetaprogrammingRecords'] }],
18
+ ['module', 'moduleConstraints', 'sourceModules', 'targetModules', ['moduleConstraint', 'module-constraint']],
19
+ ['scopeBinding', 'scopeBindingConstraints', 'sourceBindings', 'targetBindings', ['scopeBindingConstraint', 'scope-binding', 'scope-binding-constraint'], { extraSourceKeys: ['sourceScopeBindingRecords'], extraTargetKeys: ['targetScopeBindingRecords'] }],
20
+ ['numericSemantics', 'numericSemanticsConstraints', 'sourceNumerics', 'targetNumerics', ['numericSemanticsConstraint', 'numeric-semantics', 'numeric-semantics-constraint'], { extraSourceKeys: ['sourceNumericSemanticsRecords'], extraTargetKeys: ['targetNumericSemanticsRecords'] }],
21
+ ['textSemantics', 'textSemanticsConstraints', 'sourceTexts', 'targetTexts', ['textSemanticsConstraint', 'text-semantics', 'text-semantics-constraint'], { extraSourceKeys: ['sourceTextSemanticsRecords'], extraTargetKeys: ['targetTextSemanticsRecords'] }],
22
+ ['collectionSemantics', 'collectionSemanticsConstraints', 'sourceCollections', 'targetCollections', ['collectionSemanticsConstraint', 'collection-semantics', 'collection-semantics-constraint'], { extraSourceKeys: ['sourceCollectionSemanticsRecords'], extraTargetKeys: ['targetCollectionSemanticsRecords'] }],
23
+ ['serializationSemantics', 'serializationSemanticsConstraints', 'sourceSerializations', 'targetSerializations', ['serializationSemanticsConstraint', 'serialization-semantics', 'serialization-semantics-constraint'], { extraSourceKeys: ['sourceSerializationSemanticsRecords'], extraTargetKeys: ['targetSerializationSemanticsRecords'] }],
24
+ ['dependencySemantics', 'dependencySemanticsConstraints', 'sourceDependencies', 'targetDependencies', ['dependencySemanticsConstraint', 'dependency-semantics', 'dependency-semantics-constraint'], { extraSourceKeys: ['sourceDependencySemanticsRecords'], extraTargetKeys: ['targetDependencySemanticsRecords'] }],
25
+ ['objectModel', 'objectModelConstraints', 'sourceObjects', 'targetObjects', ['objectModelConstraint', 'object-model', 'object-model-constraint'], { extraSourceKeys: ['sourceObjectModelRecords'], extraTargetKeys: ['targetObjectModelRecords'] }],
26
+ ['protocol', 'protocolConstraints', 'sourceProtocols', 'targetProtocols', ['protocolConstraint', 'protocol-constraint']]
27
+ ];
28
+
29
+ const WORD_FIELDS = [
30
+ ['symbolId', 'symbol', 'symbolId'], ['symbolName'], ['localName'], ['ownerId', 'owner', 'ownerId'], ['ownerKind'],
31
+ ['bindingKind'], ['referenceKind'], ['scopeId', 'scopeId', 'scope'], ['ownerScopeId'], ['bindingId', 'bindingId', 'binding'],
32
+ ['externalBindingId', 'externalBindingId', 'externalBinding'], ['resolvedBindingId', 'resolvedBindingId', 'resolvedBinding'],
33
+ ['declarationId', 'declarationId', 'declaration'], ['referenceId', 'referenceId', 'reference'], ['occurrenceId', 'occurrenceId', 'occurrence'],
34
+ ['resolvedName'], ['namespace'], ['scopeType'], ['variableScopeType'], ['lookupKind'], ['resolutionKind', 'resolutionKind', 'moduleResolutionKind'],
35
+ ['moduleKind'], ['edgeKind'], ['declarationKind'], ['specifier', 'specifier', 'moduleSpecifier'], ['moduleSpecifier', 'moduleSpecifier', 'specifier'],
36
+ ['importedName', 'importedName', 'importName'], ['importName', 'importName', 'importedName'], ['exportedName', 'exportedName', 'exportName'],
37
+ ['exportName', 'exportName', 'exportedName'], ['reExportedName', 'reExportedName', 'reexportedName'], ['packageName', 'packageName', 'package'],
38
+ ['packageSubpath', 'packageSubpath', 'subpath'], ['packageCondition', 'packageCondition', 'condition'], ['packageExportKey'], ['packageImportKey'],
39
+ ['resolvedPath', 'resolvedPath', 'targetPath'], ['targetPath', 'targetPath', 'resolvedPath'], ['capability'], ['hostKind'], ['runtimeKind'],
40
+ ['apiName', 'apiName', 'callee'], ['globalName', 'globalName', 'global', 'objectName'], ['permission', 'permission', 'permissionKind'],
41
+ ['permissionKind', 'permissionKind', 'permission'], ['effectKind'], ['memoryKind'],
42
+ ['operationKind'], ['memoryOrder', 'memoryOrder', 'ordering'], ['ordering', 'ordering', 'memoryOrder'], ['lockId', 'lockId', 'lock'],
43
+ ['channelId', 'channelId', 'channel'], ['actorId', 'actorId', 'actor'], ['synchronizationKey', 'synchronizationKey', 'syncKey'],
44
+ ['callableId', 'callableId', 'callable'], ['functionId', 'functionId', 'function'], ['receiverId', 'receiverId', 'receiver'],
45
+ ['abi'], ['callingConvention'], ['layoutKind'], ['endian'], ['objectKind'], ['memberKind'], ['protocolId', 'protocolId', 'protocol'],
46
+ ['traitId', 'traitId', 'trait'], ['interfaceId', 'interfaceId', 'interface'], ['mode', 'mode', 'loanMode'], ['aliasKind'], ['moveKind'],
47
+ ['dropKind'], ['resourceKind'], ['scopeKind'], ['typeKind'], ['signatureHash'], ['contractHash'], ['typeHash'], ['flowKind'],
48
+ ['controlFlowKind'], ['sourceControlFlowId'], ['sourceId', 'from', 'sourceId'], ['targetId', 'to', 'targetId'], ['label'],
49
+ ['conditionHash'], ['orderingKey', 'orderingKey', 'orderKey'], ['lifetimeKind'], ['lifetimeRegionId', 'lifetimeRegion', 'lifetimeRegionId'],
50
+ ['regionKind'], ['resourceId', 'resource', 'resourceId'], ['sourcePath', 'sourcePath', 'path'], ['sourceHash'], ['target', 'effectTarget', 'targetResource']
51
+ ];
52
+ const LIST_FIELDS = [
53
+ ['factKinds', 'fact', 'facts', 'factKind', 'factKinds'], ['importAttributes', 'importAttribute', 'importAttributes', 'assertion', 'assertions'],
54
+ ['reads', 'read', 'reads'], ['writes', 'write', 'writes'], ['evidenceIds', 'evidence', 'evidenceIds']
55
+ ];
56
+ const NUMBER_FIELDS = [['arity'], ['size'], ['alignment']];
57
+ const FLAG_FIELDS = ['nullable', 'optional', 'publicContract', 'closure', 'captured', 'writeExpr', 'mutable', 'shadowed', 'hoisted', 'typeOnly', 'isTypeReference', 'isValueReference', 'shared', 'volatile', 'atomic', 'adapterRequired', 'async', 'generator', 'exceptional', 'cancellable'];
58
+
59
+ export const FAMILIES = Object.freeze(Object.fromEntries(FAMILY_ROWS.flatMap(([name, field, sourceKey, targetKey, aliases = [], extra = {}]) => {
60
+ const config = { field, sourceKey, targetKey, ...extra };
61
+ return [name, ...aliases].map((alias) => [alias, config]);
62
+ })));
63
+
64
+ export function parseConstraintRecord(name, text, role) {
65
+ const kind = wordAny(text, 'kind', 'constraintKind');
66
+ return cleanRecord({
67
+ id: wordAny(text, 'recordId') ?? idFrom(text, `constraint_record_${name}`),
68
+ role,
69
+ kind,
70
+ name: wordAny(text, 'name') ?? name,
71
+ constraintKind: wordAny(text, 'constraintKind'),
72
+ constraintKinds: listAny(text, 'constraint', 'constraints', 'constraintKind', 'constraintKinds') ?? (kind ? [kind] : undefined),
73
+ ...readMapped(text, WORD_FIELDS, wordAny),
74
+ ...readMapped(text, LIST_FIELDS, listAny),
75
+ ...readMapped(text, NUMBER_FIELDS, numberAny),
76
+ ...Object.fromEntries(FLAG_FIELDS.map((field) => [field, flag(field, text)])),
77
+ predicate: quoted('predicate', text) ?? wordAny(text, 'predicate'),
78
+ metadata: { name }
79
+ });
80
+ }
81
+
82
+ function readMapped(text, specs, reader) {
83
+ return Object.fromEntries(specs.map(([key, ...labels]) => [key, reader(text, ...(labels.length ? labels : [key]))]));
84
+ }
85
+ function wordAny(text, ...labels) {
86
+ for (const label of labels) {
87
+ const value = word(label, text);
88
+ if (value !== undefined) return value;
89
+ }
90
+ return undefined;
91
+ }
92
+ function listAny(text, ...labels) {
93
+ for (const label of labels) {
94
+ const value = list(label, text);
95
+ if (value) return value;
96
+ }
97
+ return undefined;
98
+ }
99
+ function numberAny(text, ...labels) {
100
+ const value = wordAny(text, ...labels);
101
+ return value === undefined ? undefined : Number(value);
102
+ }
103
+ function idFrom(text, fallback) { return /@id\(\s*["']([^"']+)["']\s*\)/.exec(text)?.[1] ?? fallback; }
104
+ function word(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
105
+ function quoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
106
+ function flag(label, text) { return new RegExp('(?:^|\\s)' + label + '(?:\\s|$)').test(text) || undefined; }
107
+ function list(label, text) {
108
+ const value = new RegExp('(?:^|\\s)' + label + '\\s+([^\\s]+)').exec(text)?.[1]?.trim();
109
+ return value ? value.split(/[|,]/).map((item) => item.trim()).filter(Boolean) : undefined;
110
+ }
111
+ function cleanRecord(record) {
112
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && (!Array.isArray(value) || value.length > 0)));
113
+ }
@@ -1,42 +1,4 @@
1
- const FAMILIES = {
2
- type: { field: 'typeConstraints', sourceKey: 'sourceTypes', targetKey: 'targetTypes' },
3
- typeConstraint: { field: 'typeConstraints', sourceKey: 'sourceTypes', targetKey: 'targetTypes' },
4
- resourceTransfer: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
5
- resourceTransferConstraint: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
6
- 'resource-transfer': { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
7
- ownership: { field: 'resourceTransfers', sourceKey: 'sourceGraphs', targetKey: 'targetGraphs', graph: true },
8
- controlFlow: { field: 'controlFlowConstraints', sourceKey: 'sourceControlFlows', targetKey: 'targetControlFlows' },
9
- controlFlowConstraint: { field: 'controlFlowConstraints', sourceKey: 'sourceControlFlows', targetKey: 'targetControlFlows' },
10
- lifetime: { field: 'lifetimeConstraints', sourceKey: 'sourceLifetimeConstraints', targetKey: 'targetLifetimeConstraints' },
11
- lifetimeConstraint: { field: 'lifetimeConstraints', sourceKey: 'sourceLifetimeConstraints', targetKey: 'targetLifetimeConstraints' },
12
- borrowScope: { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
13
- borrowScopeConstraint: { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
14
- 'borrow-scope': { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
15
- 'borrow-scope-constraint': { field: 'borrowScopeConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
16
- borrowChecker: { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
17
- borrowCheckerConstraint: { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
18
- 'borrow-checker': { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
19
- 'borrow-checker-constraint': { field: 'borrowCheckerConstraints', sourceKey: 'sourceBorrowScopes', targetKey: 'targetBorrowScopes' },
20
- callableBoundary: { field: 'callableBoundaryConstraints', sourceKey: 'sourceCallables', targetKey: 'targetCallables' },
21
- adtPattern: { field: 'adtPatternConstraints', sourceKey: 'sourcePatterns', targetKey: 'targetPatterns' },
22
- dataLayout: { field: 'dataLayoutConstraints', sourceKey: 'sourceLayouts', targetKey: 'targetLayouts' },
23
- effect: { field: 'effectConstraints', sourceKey: 'sourceEffects', targetKey: 'targetEffects' },
24
- concurrencyModel: { field: 'concurrencyModelConstraints', sourceKey: 'sourceConcurrencyModels', targetKey: 'targetConcurrencyModels' },
25
- errorModel: { field: 'errorModelConstraints', sourceKey: 'sourceErrors', targetKey: 'targetErrors' },
26
- evaluationModel: { field: 'evaluationModelConstraints', sourceKey: 'sourceEvaluations', targetKey: 'targetEvaluations' },
27
- hostEnvironment: { field: 'hostEnvironmentConstraints', sourceKey: 'sourceHosts', targetKey: 'targetHosts' },
28
- memoryModel: { field: 'memoryModelConstraints', sourceKey: 'sourceMemoryModels', targetKey: 'targetMemoryModels' },
29
- metaprogramming: { field: 'metaprogrammingConstraints', sourceKey: 'sourceMetaprograms', targetKey: 'targetMetaprograms' },
30
- module: { field: 'moduleConstraints', sourceKey: 'sourceModules', targetKey: 'targetModules' },
31
- scopeBinding: { field: 'scopeBindingConstraints', sourceKey: 'sourceBindings', targetKey: 'targetBindings' },
32
- numericSemantics: { field: 'numericSemanticsConstraints', sourceKey: 'sourceNumerics', targetKey: 'targetNumerics' },
33
- textSemantics: { field: 'textSemanticsConstraints', sourceKey: 'sourceTexts', targetKey: 'targetTexts' },
34
- collectionSemantics: { field: 'collectionSemanticsConstraints', sourceKey: 'sourceCollections', targetKey: 'targetCollections' },
35
- serializationSemantics: { field: 'serializationSemanticsConstraints', sourceKey: 'sourceSerializations', targetKey: 'targetSerializations' },
36
- dependencySemantics: { field: 'dependencySemanticsConstraints', sourceKey: 'sourceDependencies', targetKey: 'targetDependencies' },
37
- objectModel: { field: 'objectModelConstraints', sourceKey: 'sourceObjects', targetKey: 'targetObjects' },
38
- protocol: { field: 'protocolConstraints', sourceKey: 'sourceProtocols', targetKey: 'targetProtocols' }
39
- };
1
+ import { FAMILIES, parseConstraintRecord } from './conversion-constraint-record.js';
40
2
 
41
3
  export function parseConversionBlock(block) {
42
4
  const name = nameFrom(block.header);
@@ -133,62 +95,13 @@ function addConstraint(plan, family, name, text) {
133
95
  metadata: { name, family, authoredConversionBlockId: plan.id }
134
96
  });
135
97
  const recordKey = role === 'target' ? config.targetKey : config.sourceKey;
136
- entry[recordKey] = [config.graph ? resourceGraphFromRecord(record, entry) : record];
98
+ const recordValue = config.graph ? resourceGraphFromRecord(record, entry) : record;
99
+ for (const key of unique([recordKey, ...(role === 'target' ? config.extraTargetKeys ?? [] : config.extraSourceKeys ?? [])])) {
100
+ entry[key] = [recordValue];
101
+ }
137
102
  plan[config.field] = [...(plan[config.field] ?? []), entry];
138
103
  }
139
104
 
140
- function parseConstraintRecord(name, text, role) {
141
- const kind = readInlineWord('kind', text) ?? readInlineWord('constraintKind', text);
142
- return cleanRecord({
143
- id: readInlineWord('recordId', text) ?? idFrom(text, `constraint_record_${name}`),
144
- role,
145
- kind,
146
- name: readInlineWord('name', text) ?? name,
147
- constraintKind: readInlineWord('constraintKind', text),
148
- constraintKinds: readInlineList(text, 'constraint', 'constraints', 'constraintKind', 'constraintKinds') ?? (kind ? [kind] : undefined),
149
- factKinds: readInlineList(text, 'fact', 'facts', 'factKind', 'factKinds'),
150
- symbolId: readInlineWord('symbol', text) ?? readInlineWord('symbolId', text),
151
- symbolName: readInlineWord('symbolName', text),
152
- localName: readInlineWord('localName', text),
153
- ownerId: readInlineWord('owner', text) ?? readInlineWord('ownerId', text),
154
- ownerKind: readInlineWord('ownerKind', text),
155
- mode: readInlineWord('mode', text) ?? readInlineWord('loanMode', text),
156
- aliasKind: readInlineWord('aliasKind', text),
157
- moveKind: readInlineWord('moveKind', text),
158
- dropKind: readInlineWord('dropKind', text),
159
- resourceKind: readInlineWord('resourceKind', text),
160
- scopeKind: readInlineWord('scopeKind', text),
161
- typeKind: readInlineWord('typeKind', text),
162
- signatureHash: readInlineWord('signatureHash', text),
163
- contractHash: readInlineWord('contractHash', text),
164
- typeHash: readInlineWord('typeHash', text),
165
- flowKind: readInlineWord('flowKind', text),
166
- controlFlowKind: readInlineWord('controlFlowKind', text),
167
- sourceControlFlowId: readInlineWord('sourceControlFlowId', text),
168
- sourceId: readInlineWord('from', text) ?? readInlineWord('sourceId', text),
169
- targetId: readInlineWord('to', text) ?? readInlineWord('targetId', text),
170
- label: readInlineWord('label', text),
171
- conditionHash: readInlineWord('conditionHash', text),
172
- orderingKey: readInlineWord('orderingKey', text) ?? readInlineWord('orderKey', text),
173
- lifetimeKind: readInlineWord('lifetimeKind', text),
174
- lifetimeRegionId: readInlineWord('lifetimeRegion', text) ?? readInlineWord('lifetimeRegionId', text),
175
- regionKind: readInlineWord('regionKind', text),
176
- predicate: readInlineQuoted('predicate', text) ?? readInlineWord('predicate', text),
177
- resourceId: readInlineWord('resource', text) ?? readInlineWord('resourceId', text),
178
- sourcePath: readInlineWord('sourcePath', text) ?? readInlineWord('path', text),
179
- sourceHash: readInlineWord('sourceHash', text),
180
- evidenceIds: readInlineList(text, 'evidence', 'evidenceIds'),
181
- nullable: readInlineFlag('nullable', text),
182
- optional: readInlineFlag('optional', text),
183
- publicContract: readInlineFlag('publicContract', text),
184
- async: readInlineFlag('async', text),
185
- generator: readInlineFlag('generator', text),
186
- exceptional: readInlineFlag('exceptional', text),
187
- cancellable: readInlineFlag('cancellable', text),
188
- metadata: { name }
189
- });
190
- }
191
-
192
105
  function resourceGraphFromRecord(record, entry) {
193
106
  const tokens = lowerTokens(record);
194
107
  const resourceId = record.resourceId ?? record.symbolId ?? `${record.id}_resource`;
package/dist/view.js CHANGED
@@ -34,20 +34,32 @@ function readViewEvents(body) {
34
34
  }
35
35
 
36
36
  function readRenderNodes(body) {
37
- return readNestedBlocks('render', body).map((block) => {
38
- const tagName = nameFrom(block.header);
39
- const props = readRenderProps(block.body);
40
- const events = readRenderEvents(block.body);
41
- return {
42
- id: idFrom(block.header, `render_${tagName}`),
43
- kind: readInlineWord('kind', block.header) ?? 'element',
44
- tagName,
45
- identityKey: readLine('identity', block.body) ?? readLine('key', block.body),
46
- text: readQuotedLine('text', block.body),
47
- props: props.length ? props : undefined,
48
- events: events.length ? events : undefined
49
- };
50
- });
37
+ const renders = [];
38
+ for (const block of readNestedBlocks('render', body)) pushRenderNode(block, renders);
39
+ return renders;
40
+ }
41
+
42
+ function pushRenderNode(block, renders) {
43
+ const headerName = nameFrom(block.header);
44
+ const ownBody = stripNestedBlocks('render', block.body);
45
+ const kind = readInlineWord('kind', block.header) ?? readLine('kind', ownBody) ?? 'element';
46
+ const props = readRenderProps(ownBody);
47
+ const events = readRenderEvents(ownBody);
48
+ const childBlocks = readNestedBlocks('render', block.body);
49
+ const explicitChildren = readList('children', ownBody) ?? readList('child', ownBody);
50
+ const nestedChildren = childBlocks.map((child) => idFrom(child.header, `render_${nameFrom(child.header)}`));
51
+ renders.push(compactRecord({
52
+ id: idFrom(block.header, `render_${headerName}`),
53
+ kind,
54
+ tagName: readLine('tag', ownBody) ?? readLine('tagName', ownBody) ?? (kind === 'component' || kind === 'text' ? undefined : headerName),
55
+ component: readLine('component', ownBody) ?? (kind === 'component' ? headerName : undefined),
56
+ identityKey: readLine('identity', ownBody) ?? readLine('key', ownBody),
57
+ text: readQuotedLine('text', ownBody),
58
+ props: props.length ? props : undefined,
59
+ events: events.length ? events : undefined,
60
+ children: uniqueStrings([...(explicitChildren ?? []), ...nestedChildren])
61
+ }));
62
+ for (const child of childBlocks) pushRenderNode(child, renders);
51
63
  }
52
64
 
53
65
  function readRenderProps(body) {
@@ -92,6 +104,8 @@ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n
92
104
  function readQuotedLine(label, body) { return new RegExp(`^\\s*${label}\\s+["']([^"']+)["']`, 'm').exec(body)?.[1]; }
93
105
  function readInlineWord(label, text = '') { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
94
106
  function readRenderValue(value) { const quoted = /^["']([^"']+)["']$/.exec(value.trim()); return quoted ? { value: quoted[1] } : { expression: value.trim() }; }
107
+ function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
108
+ function uniqueStrings(values) { const result = []; for (const value of values) if (value && !result.includes(value)) result.push(value); return result.length ? result : undefined; }
95
109
  function parseOptionalTypeExpression(value) { return value ? parseTypeExpression(value.trim()) : undefined; }
96
110
  function parseTypeExpression(value) {
97
111
  const text = value.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "node scripts/build.mjs",
25
- "test": "npm run build && node test/smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
25
+ "test": "npm run build && node test/smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
26
26
  "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
27
27
  "fuzz": "npm run build && node fuzz/smoke.mjs",
28
28
  "bench": "npm run build && node bench/smoke.mjs",