@llm4ts/shell 2.13.0 → 2.13.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.
@@ -406,7 +406,9 @@ export const resolveQName = (element, value) => {
406
406
  };
407
407
  // ---------------------------------------------------------------------------
408
408
  // Writing
409
- export const escapeText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
409
+ // A carriage return is written as a reference: a literal one would be
410
+ // turned into a line feed by the reader's end-of-line handling.
411
+ export const escapeText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r/g, "&#13;");
410
412
  export const escapeAttribute = (value) => escapeText(value).replace(/"/g, "&quot;");
411
413
  const qualifiedName = (prefix, local) => prefix === "" ? local : `${prefix}:${local}`;
412
414
  /**
@@ -1,4 +1,4 @@
1
- import { AttributeField, ComplexTypeDef, ElementDecl, ElementField, Facets, isBuiltin, OpenQuestion, qname, SimpleTypeDef, splitClark, XsdNamespace } from "./Catalog.js";
1
+ import { AttributeField, ComplexTypeDef, ElementDecl, ElementField, Facets, isBuiltin, ModelGroup, OpenQuestion, qname, SimpleTypeDef, splitClark, XsdNamespace } from "./Catalog.js";
2
2
  import { attribute, elements, firstChild, is, resolveQName, textOf } from "./Xml.js";
3
3
  // XSD reading in two passes. `collectSchema` reads one `xs:schema` element
4
4
  // into a collector without resolving anything across documents (imports and
@@ -13,8 +13,11 @@ export const makeSchemaCollector = () => ({
13
13
  complex: new Map(),
14
14
  simple: new Map(),
15
15
  elements: new Map(),
16
+ attributes: new Map(),
16
17
  questions: [],
17
- namespaces: new Set()
18
+ namespaces: new Set(),
19
+ anonymous: new Set(),
20
+ locations: new Map()
18
21
  });
19
22
  const xs = (element, local) => is(element, XsdNamespace, local);
20
23
  const xsChildren = (element) => elements(element).filter((child) => child.name.namespace === XsdNamespace);
@@ -42,10 +45,30 @@ const parseMinOccurs = (value) => {
42
45
  const parsed = parseOccurs(value, 1);
43
46
  return parsed === "unbounded" ? 1 : parsed;
44
47
  };
48
+ /** An XSD boolean attribute: `true` or `1`. */
49
+ const flag = (element, name) => {
50
+ const value = attribute(element, name)?.trim();
51
+ return value === "true" || value === "1";
52
+ };
53
+ const facetNames = new Set([
54
+ "enumeration",
55
+ "pattern",
56
+ "length",
57
+ "minLength",
58
+ "maxLength",
59
+ "totalDigits",
60
+ "fractionDigits",
61
+ "minInclusive",
62
+ "maxInclusive",
63
+ "minExclusive",
64
+ "maxExclusive",
65
+ "whiteSpace"
66
+ ]);
45
67
  const multiply = (left, right) => left === "unbounded" || right === "unbounded" ? "unbounded" : left * right;
46
68
  class SchemaReader {
47
69
  targetNamespace;
48
70
  qualifiedElements;
71
+ qualifiedAttributes;
49
72
  /** A schema without targetNamespace included into one: its no-namespace refs mean the includer's. */
50
73
  chameleon;
51
74
  schema;
@@ -61,6 +84,20 @@ class SchemaReader {
61
84
  this.chameleon =
62
85
  attribute(schema, "targetNamespace") === undefined && namespaceOverride !== undefined;
63
86
  this.qualifiedElements = attribute(schema, "elementFormDefault") === "qualified";
87
+ this.qualifiedAttributes = attribute(schema, "attributeFormDefault") === "qualified";
88
+ }
89
+ /**
90
+ * A name for an anonymous type at `path` (`~op/x`). A global element and
91
+ * a named type may share a local name, so a taken path gets a `#n` suffix.
92
+ */
93
+ anonymousName(path) {
94
+ const base = `~${path.replace(/^~/, "")}`;
95
+ let name = qname(this.targetNamespace, base);
96
+ for (let n = 2; this.into.anonymous.has(name); n++) {
97
+ name = qname(this.targetNamespace, `${base}#${n}`);
98
+ }
99
+ this.into.anonymous.add(name);
100
+ return name;
64
101
  }
65
102
  where(element) {
66
103
  return `${this.document}:${element.line}`;
@@ -89,7 +126,7 @@ class SchemaReader {
89
126
  const references = [];
90
127
  for (const child of xsChildren(this.schema)) {
91
128
  const name = attribute(child, "name");
92
- if (xs(child, "import") || xs(child, "include")) {
129
+ if (xs(child, "import") || xs(child, "include") || xs(child, "redefine")) {
93
130
  const location = attribute(child, "schemaLocation");
94
131
  if (location !== undefined) {
95
132
  references.push({
@@ -98,6 +135,15 @@ class SchemaReader {
98
135
  location
99
136
  });
100
137
  }
138
+ const redefined = xsChildren(child)
139
+ .filter((definition) => !xs(definition, "annotation"))
140
+ .map((definition) => attribute(definition, "name") ?? "?");
141
+ if (xs(child, "redefine") && redefined.length > 0) {
142
+ this.question("redefine", child, location ?? "?", `schema ${location ?? "?"} is included, but its redefinitions of ${redefined.join(", ")} are not applied`);
143
+ }
144
+ }
145
+ else if (xs(child, "attribute") && name !== undefined) {
146
+ this.globalAttribute(child, name);
101
147
  }
102
148
  else if (xs(child, "element") && name !== undefined) {
103
149
  this.globalElement(child, name);
@@ -119,31 +165,47 @@ class SchemaReader {
119
165
  if (attribute(element, "substitutionGroup") !== undefined) {
120
166
  this.question("substitution-group", element, name, `element ${name} joins substitution group ${attribute(element, "substitutionGroup") ?? ""}; substitutes are not modelled`);
121
167
  }
122
- if (attribute(element, "abstract") === "true") {
168
+ if (flag(element, "abstract")) {
123
169
  this.question("abstract", element, name, `element ${name} is abstract`);
124
170
  }
125
- const type = this.elementType(element, `~${name}`);
171
+ const type = this.elementType(element, name);
172
+ const fixed = attribute(element, "fixed");
173
+ this.into.locations.set(elementName, this.where(element));
126
174
  this.into.elements.set(elementName, new ElementDecl({
127
175
  name: elementName,
128
176
  type,
129
- nillable: attribute(element, "nillable") === "true",
177
+ nillable: flag(element, "nillable"),
178
+ ...(fixed === undefined ? {} : { fixed }),
130
179
  ...optionalDoc(documentationOf(element))
131
180
  }));
132
181
  }
182
+ globalAttribute(element, name) {
183
+ const inline = firstChild(element, XsdNamespace, "simpleType");
184
+ let type = this.typeRef(element, attribute(element, "type"));
185
+ if (type === undefined && inline !== undefined) {
186
+ type = this.anonymousName(`@${name}`);
187
+ this.simpleType(inline, type, true);
188
+ }
189
+ this.into.attributes.set(qname(this.targetNamespace, name), {
190
+ type: type ?? stringType,
191
+ fixed: attribute(element, "fixed"),
192
+ documentation: documentationOf(element)
193
+ });
194
+ }
133
195
  /** The type of an element: its `type=` or its inline (anonymous) type. */
134
- elementType(element, anonymousLocal) {
196
+ elementType(element, anonymousPath) {
135
197
  const declared = this.typeRef(element, attribute(element, "type"));
136
198
  if (declared !== undefined)
137
199
  return declared;
138
200
  const inlineComplex = firstChild(element, XsdNamespace, "complexType");
139
201
  if (inlineComplex !== undefined) {
140
- const name = qname(this.targetNamespace, anonymousLocal);
202
+ const name = this.anonymousName(anonymousPath);
141
203
  this.complexType(inlineComplex, name, true);
142
204
  return name;
143
205
  }
144
206
  const inlineSimple = firstChild(element, XsdNamespace, "simpleType");
145
207
  if (inlineSimple !== undefined) {
146
- const name = qname(this.targetNamespace, anonymousLocal);
208
+ const name = this.anonymousName(anonymousPath);
147
209
  this.simpleType(inlineSimple, name, true);
148
210
  return name;
149
211
  }
@@ -151,48 +213,69 @@ class SchemaReader {
151
213
  }
152
214
  complexType(element, name, anonymous) {
153
215
  const local = splitClark(name).local;
154
- if (attribute(element, "mixed") === "true") {
216
+ const complexContent = firstChild(element, XsdNamespace, "complexContent");
217
+ const simpleContent = firstChild(element, XsdNamespace, "simpleContent");
218
+ if (flag(element, "mixed") || (complexContent !== undefined && flag(complexContent, "mixed"))) {
155
219
  this.question("mixed-content", element, local, `type ${local} allows mixed text content`);
156
220
  }
157
- if (attribute(element, "abstract") === "true") {
221
+ if (flag(element, "abstract")) {
158
222
  this.question("abstract", element, local, `type ${local} is abstract`);
159
223
  }
160
224
  let base;
225
+ let derivation;
161
226
  let textType;
162
227
  let content = element;
163
- const complexContent = firstChild(element, XsdNamespace, "complexContent");
164
- const simpleContent = firstChild(element, XsdNamespace, "simpleContent");
165
228
  if (complexContent !== undefined) {
166
229
  const extension = firstChild(complexContent, XsdNamespace, "extension");
167
230
  const restriction = firstChild(complexContent, XsdNamespace, "restriction");
168
231
  if (extension !== undefined) {
169
232
  base = this.typeRef(extension, attribute(extension, "base"));
233
+ derivation = "extension";
170
234
  content = extension;
171
235
  }
172
236
  else if (restriction !== undefined) {
173
237
  this.question("complex-restriction", restriction, local, `type ${local} restricts ${attribute(restriction, "base") ?? "?"}; only the restated content is modelled`);
238
+ base = this.typeRef(restriction, attribute(restriction, "base"));
239
+ derivation = "restriction";
174
240
  content = restriction;
175
241
  }
176
242
  }
177
243
  else if (simpleContent !== undefined) {
178
- const derivation = firstChild(simpleContent, XsdNamespace, "extension") ??
179
- firstChild(simpleContent, XsdNamespace, "restriction");
180
- if (derivation !== undefined) {
244
+ const extension = firstChild(simpleContent, XsdNamespace, "extension");
245
+ const restriction = firstChild(simpleContent, XsdNamespace, "restriction");
246
+ const step = extension ?? restriction;
247
+ if (step !== undefined) {
181
248
  // The base may be a simple type (the text's type) or a complex type
182
249
  // with simple content (inherit its text type and attributes); which
183
250
  // one is only known once every schema is read.
184
- base = this.typeRef(derivation, attribute(derivation, "base")) ?? stringType;
251
+ base = this.typeRef(step, attribute(step, "base")) ?? stringType;
252
+ derivation = extension === undefined ? "restriction" : "extension";
185
253
  textType = base;
186
- content = derivation;
254
+ content = step;
255
+ const inline = firstChild(step, XsdNamespace, "simpleType");
256
+ const facets = xsChildren(step).some((child) => facetNames.has(child.name.local));
257
+ if (extension === undefined && (facets || inline !== undefined)) {
258
+ // A restriction narrows the text: an anonymous simple type over
259
+ // the base's text type (resolved in the finish pass) keeps its facets.
260
+ textType = this.anonymousName(`${local}/#text`);
261
+ this.simpleType(step, textType, true, base);
262
+ }
187
263
  }
188
264
  }
189
265
  const fields = [];
266
+ const groups = [];
190
267
  const attributes = [];
191
268
  let counter = 0;
192
269
  const next = () => counter++;
193
270
  for (const child of xsChildren(content)) {
194
271
  if (xs(child, "sequence") || xs(child, "all") || xs(child, "choice")) {
195
- this.particle(child, local, fields, { choice: undefined, branch: undefined, optional: false, repeats: 1 }, next);
272
+ this.particle(child, local, fields, groups, {
273
+ choice: undefined,
274
+ branch: undefined,
275
+ group: undefined,
276
+ optional: false,
277
+ repeats: 1
278
+ }, next);
196
279
  }
197
280
  else if (xs(child, "group")) {
198
281
  this.question("group-ref", child, local, `type ${local} uses model group ${attribute(child, "ref") ?? "?"}; its fields are not modelled`);
@@ -203,7 +286,9 @@ class SchemaReader {
203
286
  name,
204
287
  anonymous,
205
288
  base,
289
+ derivation,
206
290
  fields,
291
+ groups,
207
292
  attributes,
208
293
  textType,
209
294
  simpleContent: simpleContent !== undefined,
@@ -211,21 +296,33 @@ class SchemaReader {
211
296
  location: this.where(element)
212
297
  });
213
298
  }
214
- particle(group, owner, into, context, next) {
215
- const optional = context.optional || attribute(group, "minOccurs") === "0";
216
- const repeats = multiply(context.repeats, parseOccurs(attribute(group, "maxOccurs"), 1));
299
+ particle(group, owner, into, groups, context, next) {
300
+ const minOccurs = parseMinOccurs(attribute(group, "minOccurs"));
301
+ const maxOccurs = parseOccurs(attribute(group, "maxOccurs"), 1);
302
+ const optional = context.optional || minOccurs === 0;
303
+ const repeats = multiply(context.repeats, maxOccurs);
217
304
  const isChoice = xs(group, "choice");
218
305
  const choice = isChoice ? next() : context.choice;
306
+ // The exact content model: every group, with its own bounds and parent.
307
+ const id = groups.length;
308
+ groups.push(new ModelGroup({
309
+ id,
310
+ kind: isChoice ? "choice" : xs(group, "all") ? "all" : "sequence",
311
+ minOccurs,
312
+ maxOccurs,
313
+ ...(context.group === undefined ? {} : { parent: context.group }),
314
+ start: into.length
315
+ }));
219
316
  for (const child of xsChildren(group)) {
220
317
  // Every alternative of a choice is its own branch; inside a branch,
221
318
  // a nested sequence keeps the branch it belongs to.
222
319
  const branch = isChoice ? next() : context.branch;
223
- const inner = { choice, branch, optional, repeats };
320
+ const inner = { choice, branch, group: id, optional, repeats };
224
321
  if (xs(child, "element")) {
225
322
  into.push(this.localElement(child, owner, inner));
226
323
  }
227
324
  else if (xs(child, "sequence") || xs(child, "choice") || xs(child, "all")) {
228
- this.particle(child, owner, into, inner, next);
325
+ this.particle(child, owner, into, groups, inner, next);
229
326
  }
230
327
  else if (xs(child, "any")) {
231
328
  this.question("xsd-any", child, owner, `type ${owner} accepts any element (namespace ${attribute(child, "namespace") ?? "##any"}); its content is open`);
@@ -237,14 +334,19 @@ class SchemaReader {
237
334
  }
238
335
  localElement(element, owner, context) {
239
336
  const ref = this.typeRef(element, attribute(element, "ref"));
240
- const minOccurs = context.optional ? 0 : parseMinOccurs(attribute(element, "minOccurs"));
241
- const maxOccurs = multiply(parseOccurs(attribute(element, "maxOccurs"), 1), context.repeats);
337
+ const own = {
338
+ min: parseMinOccurs(attribute(element, "minOccurs")),
339
+ max: parseOccurs(attribute(element, "maxOccurs"), 1)
340
+ };
242
341
  const base = {
243
- minOccurs,
244
- maxOccurs,
245
- nillable: attribute(element, "nillable") === "true",
342
+ minOccurs: context.optional ? 0 : own.min,
343
+ maxOccurs: multiply(own.max, context.repeats),
344
+ own,
345
+ nillable: flag(element, "nillable"),
346
+ fixed: attribute(element, "fixed"),
246
347
  choice: context.choice,
247
348
  branch: context.branch,
349
+ group: context.group ?? 0,
248
350
  documentation: documentationOf(element),
249
351
  location: this.where(element)
250
352
  };
@@ -259,26 +361,49 @@ class SchemaReader {
259
361
  ...base,
260
362
  name,
261
363
  namespace: qualified ? this.targetNamespace : "",
262
- type: this.elementType(element, `~${owner.replace(/^~/, "")}/${name}`),
364
+ type: this.elementType(element, `${owner}/${name}`),
263
365
  ref: undefined
264
366
  };
265
367
  }
266
368
  attributes(content, owner, into) {
267
369
  for (const child of xsChildren(content)) {
268
370
  if (xs(child, "attribute")) {
269
- const name = attribute(child, "name") ?? attribute(child, "ref") ?? "?";
371
+ const common = {
372
+ use: attribute(child, "use"),
373
+ fixed: attribute(child, "fixed"),
374
+ documentation: documentationOf(child),
375
+ location: this.where(child)
376
+ };
377
+ const ref = this.typeRef(child, attribute(child, "ref"));
378
+ if (ref !== undefined) {
379
+ // A global attribute: its name, type and namespace are the
380
+ // declaration's, looked up once every schema is read.
381
+ const target = splitClark(ref);
382
+ into.push({
383
+ ...common,
384
+ name: target.local,
385
+ namespace: target.namespace === "" ? undefined : target.namespace,
386
+ type: stringType,
387
+ ref
388
+ });
389
+ continue;
390
+ }
391
+ const name = attribute(child, "name") ?? "?";
270
392
  const inline = firstChild(child, XsdNamespace, "simpleType");
271
393
  let type = this.typeRef(child, attribute(child, "type"));
272
394
  if (type === undefined && inline !== undefined) {
273
- type = qname(this.targetNamespace, `~${owner.replace(/^~/, "")}/@${name}`);
395
+ type = this.anonymousName(`${owner}/@${name}`);
274
396
  this.simpleType(inline, type, true);
275
397
  }
276
- into.push(new AttributeField({
398
+ const form = attribute(child, "form");
399
+ const qualified = form === undefined ? this.qualifiedAttributes : form === "qualified";
400
+ into.push({
401
+ ...common,
277
402
  name,
403
+ namespace: qualified && this.targetNamespace !== "" ? this.targetNamespace : undefined,
278
404
  type: type ?? stringType,
279
- required: attribute(child, "use") === "required",
280
- ...optionalDoc(documentationOf(child))
281
- }));
405
+ ref: undefined
406
+ });
282
407
  }
283
408
  else if (xs(child, "attributeGroup")) {
284
409
  this.question("attribute-group", child, owner, `type ${owner} uses attribute group ${attribute(child, "ref") ?? "?"}; its attributes are not modelled`);
@@ -288,24 +413,59 @@ class SchemaReader {
288
413
  }
289
414
  }
290
415
  }
291
- simpleType(element, name, anonymous) {
416
+ /**
417
+ * A simple type from its `xs:simpleType` element, or (with `restrictionBase`)
418
+ * from a simpleContent restriction whose base is only known later.
419
+ */
420
+ simpleType(element, name, anonymous, restrictionBase) {
292
421
  const local = splitClark(name).local;
293
- const restriction = firstChild(element, XsdNamespace, "restriction");
422
+ this.into.locations.set(name, this.where(element));
423
+ const restriction = restrictionBase === undefined ? firstChild(element, XsdNamespace, "restriction") : element;
424
+ const list = firstChild(element, XsdNamespace, "list");
425
+ if (restriction === undefined && list !== undefined) {
426
+ // A list: whitespace-separated items of the item type; length facets
427
+ // of a restriction over it count items.
428
+ const inline = firstChild(list, XsdNamespace, "simpleType");
429
+ let itemType = this.typeRef(list, attribute(list, "itemType"));
430
+ if (itemType === undefined && inline !== undefined) {
431
+ itemType = this.anonymousName(`${local}/#item`);
432
+ this.simpleType(inline, itemType, true);
433
+ }
434
+ this.into.simple.set(name, new SimpleTypeDef({
435
+ name,
436
+ anonymous,
437
+ base: stringType,
438
+ facets: new Facets({}),
439
+ variety: "list",
440
+ itemType: itemType ?? stringType,
441
+ ...optionalDoc(documentationOf(element))
442
+ }));
443
+ return;
444
+ }
294
445
  if (restriction === undefined) {
295
- this.question("list-or-union", element, local, `simple type ${local} is a list or union; values are treated as strings`);
446
+ this.question("list-or-union", element, local, `simple type ${local} is a union; values are treated as strings`);
296
447
  this.into.simple.set(name, new SimpleTypeDef({
297
448
  name,
298
449
  anonymous,
299
450
  base: stringType,
300
451
  facets: new Facets({}),
452
+ variety: "union",
301
453
  ...optionalDoc(documentationOf(element))
302
454
  }));
303
455
  return;
304
456
  }
457
+ // The base is `base=`, or an anonymous simple type written inside.
458
+ let base = this.typeRef(restriction, attribute(restriction, "base")) ?? restrictionBase;
459
+ const inline = firstChild(restriction, XsdNamespace, "simpleType");
460
+ if (inline !== undefined && (base === undefined || restrictionBase !== undefined)) {
461
+ const inlineName = this.anonymousName(`${local}/#base`);
462
+ this.simpleType(inline, inlineName, true);
463
+ base = inlineName;
464
+ }
305
465
  this.into.simple.set(name, new SimpleTypeDef({
306
466
  name,
307
467
  anonymous,
308
- base: this.typeRef(restriction, attribute(restriction, "base")) ?? stringType,
468
+ base: base ?? stringType,
309
469
  facets: readFacets(restriction),
310
470
  ...optionalDoc(documentationOf(element))
311
471
  }));
@@ -337,6 +497,7 @@ const readFacets = (restriction) => {
337
497
  const maxLength = intFacet(restriction, "maxLength");
338
498
  const totalDigits = intFacet(restriction, "totalDigits");
339
499
  const fractionDigits = intFacet(restriction, "fractionDigits");
500
+ const whiteSpace = stringFacet(restriction, "whiteSpace")?.trim();
340
501
  return new Facets({
341
502
  ...(enumeration.length > 0 ? { enumeration } : {}),
342
503
  ...(pattern.length > 0 ? { pattern } : {}),
@@ -348,7 +509,10 @@ const readFacets = (restriction) => {
348
509
  ...(minInclusive === undefined ? {} : { minInclusive }),
349
510
  ...(maxInclusive === undefined ? {} : { maxInclusive }),
350
511
  ...(minExclusive === undefined ? {} : { minExclusive }),
351
- ...(maxExclusive === undefined ? {} : { maxExclusive })
512
+ ...(maxExclusive === undefined ? {} : { maxExclusive }),
513
+ ...(whiteSpace === "preserve" || whiteSpace === "replace" || whiteSpace === "collapse"
514
+ ? { whiteSpace }
515
+ : {})
352
516
  });
353
517
  };
354
518
  /** Read one `xs:schema` into the collector; returns the imports/includes to follow. */
@@ -368,9 +532,10 @@ export const finishSchemas = (collector) => {
368
532
  detail: `type ${name} is not defined in any schema that was read`
369
533
  }));
370
534
  };
371
- const resolveField = (field, owner, offset) => {
535
+ const resolveField = (field, owner, offset, groupOffset) => {
372
536
  let type = field.type ?? anyType;
373
537
  let nillable = field.nillable;
538
+ let fixed = field.fixed;
374
539
  if (field.ref !== undefined) {
375
540
  const target = collector.elements.get(field.ref);
376
541
  if (target === undefined) {
@@ -384,6 +549,7 @@ export const finishSchemas = (collector) => {
384
549
  else {
385
550
  type = target.type;
386
551
  nillable = nillable || target.nillable;
552
+ fixed = fixed ?? target.fixed;
387
553
  }
388
554
  }
389
555
  else if (!typeExists(collector, type)) {
@@ -398,9 +564,46 @@ export const finishSchemas = (collector) => {
398
564
  nillable,
399
565
  ...(field.choice === undefined ? {} : { choice: field.choice + offset }),
400
566
  ...(field.branch === undefined ? {} : { branch: field.branch + offset }),
567
+ group: field.group + groupOffset,
568
+ occurs: field.own,
569
+ ...(fixed === undefined ? {} : { fixed }),
401
570
  ...optionalDoc(field.documentation)
402
571
  });
403
572
  };
573
+ const resolveAttribute = (raw, owner) => {
574
+ if (raw.use === "prohibited")
575
+ return undefined;
576
+ let type = raw.type;
577
+ let fixed = raw.fixed;
578
+ let documentation = raw.documentation;
579
+ if (raw.ref !== undefined) {
580
+ const target = collector.attributes.get(raw.ref);
581
+ if (target === undefined) {
582
+ questions.push(new OpenQuestion({
583
+ code: "unresolved-attribute",
584
+ location: raw.location,
585
+ subject: owner,
586
+ detail: `attribute ref ${raw.ref} is not declared in any schema that was read`
587
+ }));
588
+ }
589
+ else {
590
+ type = target.type;
591
+ fixed = fixed ?? target.fixed;
592
+ documentation = documentation ?? target.documentation;
593
+ }
594
+ }
595
+ if (!typeExists(collector, type))
596
+ unresolvedType(type, raw.location, owner);
597
+ return new AttributeField({
598
+ name: raw.name,
599
+ ...(raw.namespace === undefined ? {} : { namespace: raw.namespace }),
600
+ type,
601
+ required: raw.use === "required",
602
+ ...(fixed === undefined ? {} : { fixed }),
603
+ ...optionalDoc(documentation)
604
+ });
605
+ };
606
+ const sameAttribute = (left, right) => left.name === right.name && (left.namespace ?? "") === (right.namespace ?? "");
404
607
  const flattened = new Map();
405
608
  const flatten = (raw, visiting) => {
406
609
  const done = flattened.get(raw.name);
@@ -408,6 +611,7 @@ export const finishSchemas = (collector) => {
408
611
  return done;
409
612
  const owner = splitClark(raw.name).local;
410
613
  let inheritedFields = [];
614
+ let inheritedGroups = [];
411
615
  let inheritedAttributes = [];
412
616
  let textType = raw.textType;
413
617
  if (raw.base !== undefined && !isBuiltin(raw.base)) {
@@ -418,33 +622,65 @@ export const finishSchemas = (collector) => {
418
622
  }
419
623
  else if (!visiting.has(raw.base)) {
420
624
  const base = flatten(baseRaw, new Set([...visiting, raw.name]));
421
- inheritedFields = base.fields;
625
+ // A restriction restates the content; only attributes carry over.
626
+ if (raw.derivation !== "restriction" || raw.simpleContent) {
627
+ inheritedFields = base.fields;
628
+ inheritedGroups = base.groups ?? [];
629
+ }
422
630
  inheritedAttributes = base.attributes;
423
631
  // Simple content over a complex base: the text type is the base's.
424
- if (raw.simpleContent)
632
+ if (raw.simpleContent && raw.textType === raw.base)
425
633
  textType = base.textType ?? stringType;
426
634
  }
427
635
  }
428
636
  // Choice and branch ids of the derived part continue after the base's,
429
637
  // so a base choice and a derived choice stay separate groups.
430
638
  const offset = inheritedFields.reduce((max, field) => Math.max(max, (field.choice ?? -1) + 1, (field.branch ?? -1) + 1), 0);
639
+ const groupOffset = inheritedGroups.length;
640
+ const fieldOffset = inheritedFields.length;
431
641
  if (textType !== undefined && !typeExists(collector, textType)) {
432
642
  unresolvedType(textType, raw.location, owner);
433
643
  }
434
- for (const attributeField of raw.attributes) {
435
- if (!typeExists(collector, attributeField.type)) {
436
- unresolvedType(attributeField.type, raw.location, owner);
644
+ // Derived attributes restate (or prohibit) inherited ones of the same name.
645
+ const attributes = [];
646
+ for (const inherited of inheritedAttributes) {
647
+ const restated = raw.attributes.find((candidate) => sameAttribute(inherited, candidate));
648
+ if (restated === undefined)
649
+ attributes.push(inherited);
650
+ else {
651
+ const resolved = resolveAttribute(restated, owner);
652
+ if (resolved !== undefined)
653
+ attributes.push(resolved);
437
654
  }
438
655
  }
656
+ for (const declared of raw.attributes) {
657
+ if (inheritedAttributes.some((inherited) => sameAttribute(inherited, declared)))
658
+ continue;
659
+ const resolved = resolveAttribute(declared, owner);
660
+ if (resolved !== undefined)
661
+ attributes.push(resolved);
662
+ }
663
+ const groups = [
664
+ ...inheritedGroups,
665
+ ...raw.groups.map((group) => new ModelGroup({
666
+ ...group,
667
+ id: group.id + groupOffset,
668
+ ...(group.parent === undefined ? {} : { parent: group.parent + groupOffset }),
669
+ start: group.start + fieldOffset
670
+ }))
671
+ ];
439
672
  const result = new ComplexTypeDef({
440
673
  name: raw.name,
441
674
  anonymous: raw.anonymous,
442
- ...(raw.base === undefined ? {} : { base: raw.base }),
675
+ ...(raw.base === undefined || (raw.derivation === "restriction" && !raw.simpleContent)
676
+ ? {}
677
+ : { base: raw.base }),
443
678
  fields: [
444
679
  ...inheritedFields,
445
- ...raw.fields.map((field) => resolveField(field, owner, offset))
680
+ ...raw.fields.map((field) => resolveField(field, owner, offset, groupOffset))
446
681
  ],
447
- attributes: [...inheritedAttributes, ...raw.attributes],
682
+ ...(groups.length === 0 ? {} : { groups }),
683
+ attributes,
448
684
  ...(textType === undefined ? {} : { textType }),
449
685
  ...optionalDoc(raw.documentation)
450
686
  });
@@ -452,15 +688,28 @@ export const finishSchemas = (collector) => {
452
688
  return result;
453
689
  };
454
690
  const complex = [...collector.complex.values()].map((raw) => flatten(raw, new Set()));
455
- const simple = [...collector.simple.values()];
691
+ // A simpleContent restriction's text type restricts its complex base's
692
+ // text type, known only now.
693
+ const simple = [...collector.simple.values()].map((type) => {
694
+ const complexBase = collector.complex.get(type.base);
695
+ if (complexBase === undefined)
696
+ return type;
697
+ return new SimpleTypeDef({
698
+ ...type,
699
+ base: flatten(complexBase, new Set()).textType ?? stringType
700
+ });
701
+ });
456
702
  for (const type of simple) {
457
- if (!typeExists(collector, type.base)) {
458
- unresolvedType(type.base, "(simple type)", splitClark(type.name).local);
703
+ const types = [type.base, ...(type.itemType === undefined ? [] : [type.itemType])];
704
+ for (const name of types) {
705
+ if (!typeExists(collector, name)) {
706
+ unresolvedType(name, collector.locations.get(type.name) ?? "(simple type)", splitClark(type.name).local);
707
+ }
459
708
  }
460
709
  }
461
710
  for (const element of collector.elements.values()) {
462
711
  if (!typeExists(collector, element.type)) {
463
- unresolvedType(element.type, "(global element)", splitClark(element.name).local);
712
+ unresolvedType(element.type, collector.locations.get(element.name) ?? "(global element)", splitClark(element.name).local);
464
713
  }
465
714
  }
466
715
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/shell",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,9 +52,9 @@
52
52
  "dependencies": {
53
53
  "@effect/platform-node": "4.0.0-rc.115",
54
54
  "@effect/platform-node-shared": "4.0.0-rc.115",
55
- "@llm4ts/flow": "2.13.0",
56
- "@llm4ts/runner": "2.13.0",
57
- "@llm4ts/core": "2.13.0"
55
+ "@llm4ts/core": "2.13.1",
56
+ "@llm4ts/runner": "2.13.1",
57
+ "@llm4ts/flow": "2.13.1"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "effect": "4.0.0-rc.115"