@githolon/dsl 0.107.4 → 0.108.1
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/dart/CHANGELOG.md +24 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +10634 -6122
- package/dart/lib/src/runner/offline-install.mjs +4187 -2716
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +373 -282
- package/dart/lib/src/webview_bridge.dart +2146 -725
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +17 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +11 -0
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +17945 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +933 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5760 -1879
- package/src/authoring.ts +3 -2
- package/src/birth_meter_ensure.ts +2 -0
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +41 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +392 -46
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +4 -0
- package/src/framework/auth_provider_law.ts +296 -20
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +83 -40
- package/src/framework/owned_custody.ts +25 -12
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +100 -17
- package/src/framework/workspaces.ts +39 -10
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +22 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +44 -42
- package/src/stable_ids.ts +37 -2
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- package/src/workspace_sharing.ts +27 -10
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/// Constrained canonical-USDA reader used by generated Nomos domain decoders.
|
|
2
|
+
///
|
|
3
|
+
/// This is syntax infrastructure, not a second domain model. Generated clients
|
|
4
|
+
/// supply the exact aggregate and field law paths they were compiled from and
|
|
5
|
+
/// refuse any result whose relationships do not match them.
|
|
6
|
+
@internal
|
|
7
|
+
library;
|
|
8
|
+
|
|
9
|
+
import 'dart:convert';
|
|
10
|
+
import 'dart:typed_data';
|
|
11
|
+
|
|
12
|
+
import 'package:meta/meta.dart' show internal;
|
|
13
|
+
|
|
14
|
+
final class NmsUsdaProperty {
|
|
15
|
+
const NmsUsdaProperty(this.name, this.value);
|
|
16
|
+
|
|
17
|
+
final String name;
|
|
18
|
+
final Object value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
final class NmsUsdaPrim {
|
|
22
|
+
NmsUsdaPrim(this.typeName, this.name);
|
|
23
|
+
|
|
24
|
+
final String typeName;
|
|
25
|
+
final String name;
|
|
26
|
+
final List<NmsUsdaProperty> properties = [];
|
|
27
|
+
final List<NmsUsdaPrim> children = [];
|
|
28
|
+
|
|
29
|
+
Object? property(String propertyName) {
|
|
30
|
+
for (final property in properties) {
|
|
31
|
+
if (property.name == propertyName) return property.value;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
String? relationship(String propertyName) {
|
|
37
|
+
final value = property(propertyName);
|
|
38
|
+
return value is String ? value : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
String requiredStringProperty(String propertyName) {
|
|
42
|
+
final value = property(propertyName);
|
|
43
|
+
if (value is! String) {
|
|
44
|
+
throw FormatException('$typeName has no textual $propertyName');
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
String? optionalStringProperty(String propertyName) {
|
|
50
|
+
final value = property(propertyName);
|
|
51
|
+
if (value == null) return null;
|
|
52
|
+
if (value is! String) {
|
|
53
|
+
throw FormatException('$typeName has a non-textual $propertyName');
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
int requiredIntProperty(String propertyName) {
|
|
59
|
+
final value = property(propertyName);
|
|
60
|
+
if (value is! int) {
|
|
61
|
+
throw FormatException('$typeName has no integer $propertyName');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
List<String> requiredStringArrayProperty(String propertyName) {
|
|
67
|
+
final value = property(propertyName);
|
|
68
|
+
if (value is! List || value.any((entry) => entry is! String)) {
|
|
69
|
+
throw FormatException('$typeName has no string array $propertyName');
|
|
70
|
+
}
|
|
71
|
+
return List.unmodifiable(value.cast<String>());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
NmsUsdaPrim? field(String lawPath) {
|
|
75
|
+
for (final child in children) {
|
|
76
|
+
if (child.relationship('nomos:field') == lawPath) return child;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
NmsUsdaPrim requiredField(String lawPath) {
|
|
82
|
+
final value = field(lawPath);
|
|
83
|
+
if (value == null) {
|
|
84
|
+
throw FormatException('USDA row omits required law field $lawPath');
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
NmsUsdaPrim? child(String childName) {
|
|
90
|
+
for (final child in children) {
|
|
91
|
+
if (child.name == childName) return child;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
NmsUsdaPrim requiredChild(String childName) {
|
|
97
|
+
final value = child(childName);
|
|
98
|
+
if (value == null) {
|
|
99
|
+
throw FormatException('$typeName has no $childName child');
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
NmsUsdaPrim? jsonMember(String key) {
|
|
105
|
+
final object = switch (typeName) {
|
|
106
|
+
'NomosRecordValue' || 'NomosJsonValue' => requiredChild('Value'),
|
|
107
|
+
'NomosJsonObject' => this,
|
|
108
|
+
_ => throw FormatException('$typeName is not a USDA object value'),
|
|
109
|
+
};
|
|
110
|
+
if (object.typeName != 'NomosJsonObject') {
|
|
111
|
+
throw FormatException('${object.typeName} is not a USDA JSON object');
|
|
112
|
+
}
|
|
113
|
+
for (final member in object.children) {
|
|
114
|
+
if (member.requiredStringProperty('nomos:key') == key) {
|
|
115
|
+
return member.requiredChild('Value');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
NmsUsdaPrim requiredJsonMember(String key) {
|
|
122
|
+
final value = jsonMember(key);
|
|
123
|
+
if (value == null)
|
|
124
|
+
throw FormatException('$typeName has no JSON member $key');
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
String stringValue() {
|
|
129
|
+
final value = property('nomos:value');
|
|
130
|
+
if (value is! String)
|
|
131
|
+
throw const FormatException('USDA value is not textual');
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
int intValue() {
|
|
136
|
+
final value = property('nomos:value');
|
|
137
|
+
if (value is! int)
|
|
138
|
+
throw const FormatException('USDA value is not an integer');
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
num numberValue() {
|
|
143
|
+
final value = property('nomos:value');
|
|
144
|
+
if (value is! num) throw const FormatException('USDA value is not numeric');
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
bool boolValue() {
|
|
149
|
+
final value = property('nomos:value');
|
|
150
|
+
if (value is! bool)
|
|
151
|
+
throw const FormatException('USDA value is not boolean');
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Object? jsonValue() {
|
|
156
|
+
switch (typeName) {
|
|
157
|
+
case 'NomosNullValue':
|
|
158
|
+
return null;
|
|
159
|
+
case 'NomosBoolValue':
|
|
160
|
+
return boolValue();
|
|
161
|
+
case 'NomosIntValue':
|
|
162
|
+
case 'NomosNumberValue':
|
|
163
|
+
return numberValue();
|
|
164
|
+
case 'NomosStringValue':
|
|
165
|
+
case 'NomosTokenValue':
|
|
166
|
+
return stringValue();
|
|
167
|
+
case 'NomosJsonValue':
|
|
168
|
+
case 'NomosRecordValue':
|
|
169
|
+
return _requiredChild('Value').jsonValue();
|
|
170
|
+
case 'NomosJsonArray':
|
|
171
|
+
return children
|
|
172
|
+
.map((child) => child.jsonValue())
|
|
173
|
+
.toList(growable: false);
|
|
174
|
+
case 'NomosJsonObject':
|
|
175
|
+
return <String, Object?>{
|
|
176
|
+
for (final member in children)
|
|
177
|
+
member._requiredStringProperty('nomos:key'):
|
|
178
|
+
member._requiredChild('Value').jsonValue(),
|
|
179
|
+
};
|
|
180
|
+
default:
|
|
181
|
+
throw FormatException('Unsupported nominal JSON prim $typeName');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Map<String, T> mapValue<T>(T Function(NmsUsdaPrim value) decode) =>
|
|
186
|
+
<String, T>{
|
|
187
|
+
for (final entry in children)
|
|
188
|
+
entry._requiredStringProperty('nomos:key'):
|
|
189
|
+
decode(entry._requiredChild('Value')),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
NmsUsdaPrim _requiredChild(String childName) {
|
|
193
|
+
for (final child in children) {
|
|
194
|
+
if (child.name == childName) return child;
|
|
195
|
+
}
|
|
196
|
+
throw FormatException('$typeName has no $childName child');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
String _requiredStringProperty(String propertyName) {
|
|
200
|
+
final value = property(propertyName);
|
|
201
|
+
if (value is! String) {
|
|
202
|
+
throw FormatException('$typeName has no textual $propertyName');
|
|
203
|
+
}
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// Syntax-level view of the framework-owned maintained conflict index. The
|
|
209
|
+
/// generated domain client supplies the application field types and law paths.
|
|
210
|
+
final class NmsUsdaConflictResult {
|
|
211
|
+
const NmsUsdaConflictResult({required this.rows});
|
|
212
|
+
|
|
213
|
+
final List<NmsUsdaPrim> rows;
|
|
214
|
+
|
|
215
|
+
factory NmsUsdaConflictResult.parse(Uint8List bytes) {
|
|
216
|
+
final root = NmsUsdaLayer.parse(bytes);
|
|
217
|
+
if (root.typeName != 'NomosPendingConflicts' || root.name != 'Conflicts') {
|
|
218
|
+
throw const FormatException('Expected NomosPendingConflicts');
|
|
219
|
+
}
|
|
220
|
+
final rows = root.requiredChild('Rows');
|
|
221
|
+
if (rows.typeName != 'NomosListValue') {
|
|
222
|
+
throw const FormatException('Pending conflict Rows is not a list');
|
|
223
|
+
}
|
|
224
|
+
return NmsUsdaConflictResult(rows: List.unmodifiable(rows.children));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
final class NmsUsdaConflictCount {
|
|
229
|
+
const NmsUsdaConflictCount({
|
|
230
|
+
required this.aggregatePath,
|
|
231
|
+
required this.count,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
final String aggregatePath;
|
|
235
|
+
final int count;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Exact framework summary. Aggregate identities remain authenticated law
|
|
239
|
+
/// relationships rather than host-authored labels.
|
|
240
|
+
final class NmsUsdaConflictSummaryResult {
|
|
241
|
+
const NmsUsdaConflictSummaryResult({
|
|
242
|
+
required this.total,
|
|
243
|
+
required this.ready,
|
|
244
|
+
required this.evolutionRequired,
|
|
245
|
+
required this.byAggregate,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
final int total;
|
|
249
|
+
final int ready;
|
|
250
|
+
final int evolutionRequired;
|
|
251
|
+
final List<NmsUsdaConflictCount> byAggregate;
|
|
252
|
+
|
|
253
|
+
factory NmsUsdaConflictSummaryResult.parse(Uint8List bytes) {
|
|
254
|
+
final root = NmsUsdaLayer.parse(bytes);
|
|
255
|
+
if (root.typeName != 'NomosPendingConflictSummary' ||
|
|
256
|
+
root.name != 'ConflictSummary') {
|
|
257
|
+
throw const FormatException('Expected NomosPendingConflictSummary');
|
|
258
|
+
}
|
|
259
|
+
int integer(String name) {
|
|
260
|
+
final value = root.property(name);
|
|
261
|
+
if (value is! int || value < 0) {
|
|
262
|
+
throw FormatException('$name is not an unsigned integer');
|
|
263
|
+
}
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
final counts = root.requiredChild('ByAggregate');
|
|
268
|
+
if (counts.typeName != 'NomosConflictCounts') {
|
|
269
|
+
throw const FormatException(
|
|
270
|
+
'Conflict summary counts have the wrong type');
|
|
271
|
+
}
|
|
272
|
+
return NmsUsdaConflictSummaryResult(
|
|
273
|
+
total: integer('nomos:total'),
|
|
274
|
+
ready: integer('nomos:ready'),
|
|
275
|
+
evolutionRequired: integer('nomos:evolutionRequired'),
|
|
276
|
+
byAggregate: List.unmodifiable(counts.children.map((entry) {
|
|
277
|
+
final aggregatePath = entry.relationship('nomos:type');
|
|
278
|
+
final count = entry.property('nomos:count');
|
|
279
|
+
if (aggregatePath == null || count is! int || count < 0) {
|
|
280
|
+
throw const FormatException('Malformed aggregate conflict count');
|
|
281
|
+
}
|
|
282
|
+
return NmsUsdaConflictCount(
|
|
283
|
+
aggregatePath: aggregatePath,
|
|
284
|
+
count: count,
|
|
285
|
+
);
|
|
286
|
+
})),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
final class NmsUsdaResult {
|
|
292
|
+
const NmsUsdaResult({required this.rows, this.cursor, required this.done});
|
|
293
|
+
|
|
294
|
+
final List<NmsUsdaPrim> rows;
|
|
295
|
+
final String? cursor;
|
|
296
|
+
final bool done;
|
|
297
|
+
|
|
298
|
+
factory NmsUsdaResult.parse(Uint8List bytes) {
|
|
299
|
+
final root = NmsUsdaLayer.parse(bytes);
|
|
300
|
+
if (root.typeName != 'NomosReadResult') {
|
|
301
|
+
throw const FormatException('Expected NomosReadResult');
|
|
302
|
+
}
|
|
303
|
+
NmsUsdaPrim? rows;
|
|
304
|
+
for (final child in root.children) {
|
|
305
|
+
if (child.typeName == 'NomosListValue' && child.name == 'Rows') {
|
|
306
|
+
rows = child;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (rows == null)
|
|
311
|
+
throw const FormatException('NomosReadResult has no Rows');
|
|
312
|
+
final cursor = root.property('nomos:cursor');
|
|
313
|
+
final done = root.property('nomos:done');
|
|
314
|
+
if (cursor != null && cursor is! String) {
|
|
315
|
+
throw const FormatException('NomosReadResult cursor is not textual');
|
|
316
|
+
}
|
|
317
|
+
if (done != null && done is! bool) {
|
|
318
|
+
throw const FormatException('NomosReadResult done marker is not boolean');
|
|
319
|
+
}
|
|
320
|
+
return NmsUsdaResult(
|
|
321
|
+
rows: List.unmodifiable(rows.children),
|
|
322
|
+
cursor: cursor as String?,
|
|
323
|
+
done: done as bool? ?? true,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Syntax-only entry point for generated nominal USDA contracts which are not
|
|
329
|
+
/// read-result pages (for example a transported framework capability).
|
|
330
|
+
@internal
|
|
331
|
+
abstract final class NmsUsdaLayer {
|
|
332
|
+
static NmsUsdaPrim parse(Uint8List bytes) => _parseLayer(bytes);
|
|
333
|
+
|
|
334
|
+
static String quote(String value) {
|
|
335
|
+
final out = StringBuffer('"');
|
|
336
|
+
for (final code in value.codeUnits) {
|
|
337
|
+
switch (code) {
|
|
338
|
+
case 0x22:
|
|
339
|
+
out.write(r'\"');
|
|
340
|
+
case 0x5c:
|
|
341
|
+
out.write(r'\\');
|
|
342
|
+
case 0x0a:
|
|
343
|
+
out.write(r'\n');
|
|
344
|
+
case 0x0d:
|
|
345
|
+
out.write(r'\r');
|
|
346
|
+
case 0x09:
|
|
347
|
+
out.write(r'\t');
|
|
348
|
+
case 0x08:
|
|
349
|
+
out.write(r'\b');
|
|
350
|
+
case 0x0c:
|
|
351
|
+
out.write(r'\f');
|
|
352
|
+
default:
|
|
353
|
+
if (code < 0x20) {
|
|
354
|
+
out.write(r'\u');
|
|
355
|
+
out.write(code.toRadixString(16).padLeft(4, '0'));
|
|
356
|
+
} else {
|
|
357
|
+
out.writeCharCode(code);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
out.write('"');
|
|
362
|
+
return out.toString();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
NmsUsdaPrim _parseLayer(Uint8List bytes) {
|
|
367
|
+
final roots = <NmsUsdaPrim>[];
|
|
368
|
+
final stack = <NmsUsdaPrim>[];
|
|
369
|
+
NmsUsdaPrim? pending;
|
|
370
|
+
for (final original in const LineSplitter().convert(utf8.decode(bytes))) {
|
|
371
|
+
final line = original.trim();
|
|
372
|
+
if (line.isEmpty ||
|
|
373
|
+
line.startsWith('#') ||
|
|
374
|
+
line == '(' ||
|
|
375
|
+
line == ')' ||
|
|
376
|
+
line.startsWith('defaultPrim')) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
final declaration = RegExp(
|
|
380
|
+
r'^def\s+([A-Za-z_][A-Za-z0-9_]*)\s+("(?:[^"\\]|\\.)*")$',
|
|
381
|
+
).firstMatch(line);
|
|
382
|
+
if (declaration != null) {
|
|
383
|
+
pending = NmsUsdaPrim(
|
|
384
|
+
declaration.group(1)!,
|
|
385
|
+
_unquote(declaration.group(2)!),
|
|
386
|
+
);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (line == '{') {
|
|
390
|
+
final prim = pending;
|
|
391
|
+
if (prim == null)
|
|
392
|
+
throw const FormatException('USDA scope has no declaration');
|
|
393
|
+
if (stack.isEmpty) {
|
|
394
|
+
roots.add(prim);
|
|
395
|
+
} else {
|
|
396
|
+
stack.last.children.add(prim);
|
|
397
|
+
}
|
|
398
|
+
stack.add(prim);
|
|
399
|
+
pending = null;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (line == '}') {
|
|
403
|
+
if (stack.isEmpty) throw const FormatException('Unbalanced USDA scope');
|
|
404
|
+
stack.removeLast();
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
final property = RegExp(
|
|
408
|
+
r'^custom(?:\s+uniform)?\s+(?:rel|[A-Za-z0-9_\[\]]+)\s+([A-Za-z0-9_:]+)\s*=\s*(.+)$',
|
|
409
|
+
).firstMatch(line);
|
|
410
|
+
if (property != null) {
|
|
411
|
+
if (stack.isEmpty)
|
|
412
|
+
throw const FormatException('USDA property outside prim');
|
|
413
|
+
stack.last.properties.add(
|
|
414
|
+
NmsUsdaProperty(property.group(1)!, _atom(property.group(2)!)),
|
|
415
|
+
);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
throw FormatException('Unsupported canonical USDA line: $line');
|
|
419
|
+
}
|
|
420
|
+
if (stack.isNotEmpty || roots.length != 1) {
|
|
421
|
+
throw const FormatException(
|
|
422
|
+
'USDA result must contain one balanced root prim');
|
|
423
|
+
}
|
|
424
|
+
return roots.single;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
Object _atom(String source) {
|
|
428
|
+
final value = source.trim();
|
|
429
|
+
if (value.startsWith('"')) return _unquote(value);
|
|
430
|
+
if (value == 'true') return true;
|
|
431
|
+
if (value == 'false') return false;
|
|
432
|
+
final integer = int.tryParse(value);
|
|
433
|
+
if (integer != null) return integer;
|
|
434
|
+
final number = double.tryParse(value);
|
|
435
|
+
if (number != null && number.isFinite) return number;
|
|
436
|
+
if (value.startsWith('<') && value.endsWith('>')) {
|
|
437
|
+
return value.substring(1, value.length - 1);
|
|
438
|
+
}
|
|
439
|
+
throw FormatException('Unsupported USDA value $value');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
String _unquote(String source) {
|
|
443
|
+
if (source.length < 2 || !source.startsWith('"') || !source.endsWith('"')) {
|
|
444
|
+
throw const FormatException('Invalid USDA string');
|
|
445
|
+
}
|
|
446
|
+
final out = StringBuffer();
|
|
447
|
+
for (var index = 1; index < source.length - 1; index++) {
|
|
448
|
+
final character = source[index];
|
|
449
|
+
if (character != r'\') {
|
|
450
|
+
out.write(character);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (++index >= source.length - 1) {
|
|
454
|
+
throw const FormatException('Truncated USDA escape');
|
|
455
|
+
}
|
|
456
|
+
final escaped = source[index];
|
|
457
|
+
switch (escaped) {
|
|
458
|
+
case 'n':
|
|
459
|
+
out.write('\n');
|
|
460
|
+
case 'r':
|
|
461
|
+
out.write('\r');
|
|
462
|
+
case 't':
|
|
463
|
+
out.write('\t');
|
|
464
|
+
case 'b':
|
|
465
|
+
out.write('\b');
|
|
466
|
+
case 'f':
|
|
467
|
+
out.write('\f');
|
|
468
|
+
case '"':
|
|
469
|
+
out.write('"');
|
|
470
|
+
case r'\':
|
|
471
|
+
out.write(r'\');
|
|
472
|
+
case 'u':
|
|
473
|
+
if (index + 4 >= source.length) {
|
|
474
|
+
throw const FormatException('Truncated USDA unicode escape');
|
|
475
|
+
}
|
|
476
|
+
final digits = source.substring(index + 1, index + 5);
|
|
477
|
+
final code = int.tryParse(digits, radix: 16);
|
|
478
|
+
if (code == null)
|
|
479
|
+
throw const FormatException('Invalid USDA unicode escape');
|
|
480
|
+
out.writeCharCode(code);
|
|
481
|
+
index += 4;
|
|
482
|
+
default:
|
|
483
|
+
throw FormatException('Unsupported USDA escape $escaped');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return out.toString();
|
|
487
|
+
}
|