@eriknaslund/skiss 0.4.0 → 0.6.0
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 +9 -4
- package/dist/cli.js +317 -30
- package/dist/index.d.ts +58 -5
- package/dist/index.js +427 -35
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
+
// src/inheritance.ts
|
|
2
|
+
function inheritance(classes) {
|
|
3
|
+
const declared = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const cls of classes) {
|
|
5
|
+
if (!declared.has(cls.name.text)) declared.set(cls.name.text, cls);
|
|
6
|
+
}
|
|
7
|
+
const circles = /* @__PURE__ */ new Map();
|
|
8
|
+
const parentOf = (cls) => {
|
|
9
|
+
if (cls.parent === void 0 || circles.has(cls)) return void 0;
|
|
10
|
+
return declared.get(cls.parent.text);
|
|
11
|
+
};
|
|
12
|
+
for (const cls of declared.values()) {
|
|
13
|
+
const path = [cls];
|
|
14
|
+
const seen = new Set(path);
|
|
15
|
+
for (let next = parentOf(cls); next !== void 0; next = parentOf(next)) {
|
|
16
|
+
if (seen.has(next)) {
|
|
17
|
+
const circle = path.slice(path.indexOf(next));
|
|
18
|
+
let closer = circle[0];
|
|
19
|
+
for (const member2 of circle) {
|
|
20
|
+
if (member2.line > closer.line) closer = member2;
|
|
21
|
+
}
|
|
22
|
+
const at = circle.indexOf(closer);
|
|
23
|
+
circles.set(closer, [...circle.slice(at), ...circle.slice(0, at)]);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
path.push(next);
|
|
27
|
+
seen.add(next);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const ancestors = (cls) => {
|
|
31
|
+
const out = [];
|
|
32
|
+
const seen = /* @__PURE__ */ new Set([cls]);
|
|
33
|
+
for (let next = parentOf(cls); next !== void 0; next = parentOf(next)) {
|
|
34
|
+
if (seen.has(next)) break;
|
|
35
|
+
out.push(next);
|
|
36
|
+
seen.add(next);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
const find = (cls, matches) => {
|
|
41
|
+
for (const from of ancestors(cls)) {
|
|
42
|
+
const field = from.fields.find(matches);
|
|
43
|
+
if (field !== void 0) return { field, from };
|
|
44
|
+
}
|
|
45
|
+
return void 0;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
ancestors,
|
|
49
|
+
inherited: (cls, fieldName) => find(cls, (field) => field.name.text === fieldName),
|
|
50
|
+
inheritedIdentifier: (cls) => find(cls, (field) => field.identifier),
|
|
51
|
+
circle: (cls) => circles.get(cls)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function sameField(child, inheritedField) {
|
|
55
|
+
return meaning(child) === meaning(inheritedField);
|
|
56
|
+
}
|
|
57
|
+
var meaning = (field) => JSON.stringify([
|
|
58
|
+
field.name.text,
|
|
59
|
+
field.identifier,
|
|
60
|
+
typeMeaning(field),
|
|
61
|
+
field.system?.text,
|
|
62
|
+
field.joinsTo === void 0 ? void 0 : [field.joinsTo.className.text, field.joinsTo.fieldName.text],
|
|
63
|
+
field.description,
|
|
64
|
+
field.note
|
|
65
|
+
]);
|
|
66
|
+
function typeMeaning(field) {
|
|
67
|
+
const type = field.type;
|
|
68
|
+
if (type === void 0) return void 0;
|
|
69
|
+
switch (type.kind) {
|
|
70
|
+
case "primitive":
|
|
71
|
+
return [type.kind, type.name, type.many];
|
|
72
|
+
case "class":
|
|
73
|
+
case "unknown":
|
|
74
|
+
return [type.kind, type.name.text, type.many];
|
|
75
|
+
case "enum":
|
|
76
|
+
return [type.kind, type.values.map((v) => v.text), type.many];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
1
80
|
// src/parse.ts
|
|
2
81
|
var isWs = (c) => c === " " || c === " ";
|
|
3
82
|
var trimWs = (s) => s.replace(/^[ \t]+/, "").replace(/[ \t]+$/, "");
|
|
@@ -21,7 +100,7 @@ function primitiveFor(word) {
|
|
|
21
100
|
return Object.hasOwn(PRIMITIVES, word) ? PRIMITIVES[word] : void 0;
|
|
22
101
|
}
|
|
23
102
|
var FIELD_ORDER = "name, `*`, `: type`, `@System`, `= Class.field`, then `#` and `?`";
|
|
24
|
-
var CLASS_ORDER = "name, `@System`, `~ Class`, then `#` and `?`";
|
|
103
|
+
var CLASS_ORDER = "name, `< Parent`, `@System`, `~ Class`, then `#` and `?`";
|
|
25
104
|
function parse(source) {
|
|
26
105
|
const classes = [];
|
|
27
106
|
const diagnostics = [];
|
|
@@ -108,7 +187,11 @@ function splitTrailer(text) {
|
|
|
108
187
|
return trailer;
|
|
109
188
|
}
|
|
110
189
|
var WORD = /[\p{L}\p{N}_-]+/uy;
|
|
111
|
-
|
|
190
|
+
function wordAt(text, i) {
|
|
191
|
+
WORD.lastIndex = i;
|
|
192
|
+
return WORD.exec(text)?.[0];
|
|
193
|
+
}
|
|
194
|
+
var OPERATORS = ["*", ":", "@", "~", "=", ".", "|", "<", ",", "["];
|
|
112
195
|
function tokenize(head) {
|
|
113
196
|
const tokens = [];
|
|
114
197
|
let i = 0;
|
|
@@ -118,11 +201,10 @@ function tokenize(head) {
|
|
|
118
201
|
i++;
|
|
119
202
|
continue;
|
|
120
203
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
i += word[0].length;
|
|
204
|
+
const word = wordAt(head, i);
|
|
205
|
+
if (word !== void 0) {
|
|
206
|
+
tokens.push({ kind: "word", text: word, col: i, end: i + word.length });
|
|
207
|
+
i += word.length;
|
|
126
208
|
continue;
|
|
127
209
|
}
|
|
128
210
|
if (c === "[" && head[i + 1] === "]") {
|
|
@@ -170,6 +252,13 @@ function parseClassLine(text, line) {
|
|
|
170
252
|
);
|
|
171
253
|
}
|
|
172
254
|
const node = { name: name(head, line), line };
|
|
255
|
+
const lt = peek(c);
|
|
256
|
+
if (lt?.kind === "<") {
|
|
257
|
+
take(c);
|
|
258
|
+
const parent = parseParent(c, lt);
|
|
259
|
+
if (!parent.ok) return parent;
|
|
260
|
+
node.parent = parent.value;
|
|
261
|
+
}
|
|
173
262
|
const at = peek(c);
|
|
174
263
|
if (at?.kind === "@") {
|
|
175
264
|
take(c);
|
|
@@ -299,6 +388,31 @@ function parseFieldLine(text, line) {
|
|
|
299
388
|
if (trailer.note !== void 0) node.note = trailer.note;
|
|
300
389
|
return { ok: true, value: node };
|
|
301
390
|
}
|
|
391
|
+
function parseParent(c, lt) {
|
|
392
|
+
const parent = take(c);
|
|
393
|
+
if (parent === void 0 || parent.kind !== "word") {
|
|
394
|
+
return fail(
|
|
395
|
+
"E_UNPARSABLE",
|
|
396
|
+
"Expected a class name after `<`, e.g. `Jedi < Character`",
|
|
397
|
+
c.line,
|
|
398
|
+
parent ?? afterSpan(lt)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (!CLASS_NAME.test(parent.text)) {
|
|
402
|
+
const message = Object.hasOwn(PRIMITIVES, parent.text) ? `\`${parent.text}\` is a primitive, not a class; a parent is a class` : `The class after \`<\` must be UpperCamelCase: \`${parent.text}\``;
|
|
403
|
+
return fail("E_BAD_NAME", message, c.line, parent);
|
|
404
|
+
}
|
|
405
|
+
const next = peek(c);
|
|
406
|
+
if (next?.kind === "," || next?.kind === "<") {
|
|
407
|
+
return fail(
|
|
408
|
+
"E_UNPARSABLE",
|
|
409
|
+
"A class has one parent; `<` takes a single class name",
|
|
410
|
+
c.line,
|
|
411
|
+
next
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return { ok: true, value: name(parent, c.line) };
|
|
415
|
+
}
|
|
302
416
|
function parseSystem(c, at) {
|
|
303
417
|
const system = take(c);
|
|
304
418
|
if (system === void 0 || system.kind !== "word") {
|
|
@@ -407,36 +521,36 @@ function parseType(c, colon) {
|
|
|
407
521
|
first
|
|
408
522
|
);
|
|
409
523
|
}
|
|
410
|
-
function unexpected(
|
|
411
|
-
const modifiers = kind === "field" ? ["*", ":", "@", "="] : ["@", "~"];
|
|
412
|
-
if (modifiers.includes(
|
|
524
|
+
function unexpected(token2, line, kind, order) {
|
|
525
|
+
const modifiers = kind === "field" ? ["*", ":", "@", "="] : ["<", "@", "~"];
|
|
526
|
+
if (modifiers.includes(token2.kind)) {
|
|
413
527
|
return fail(
|
|
414
528
|
"E_UNPARSABLE",
|
|
415
|
-
`\`${
|
|
529
|
+
`\`${token2.text}\` is out of order; a ${kind} line reads: ${order}`,
|
|
416
530
|
line,
|
|
417
|
-
|
|
531
|
+
token2
|
|
418
532
|
);
|
|
419
533
|
}
|
|
420
|
-
if (
|
|
534
|
+
if (token2.kind === "<") {
|
|
421
535
|
return fail(
|
|
422
536
|
"E_UNPARSABLE",
|
|
423
|
-
"`<`
|
|
537
|
+
"`<` marks inheritance and belongs on the class line: `Child < Parent`",
|
|
424
538
|
line,
|
|
425
|
-
|
|
539
|
+
token2
|
|
426
540
|
);
|
|
427
541
|
}
|
|
428
|
-
if (
|
|
542
|
+
if (token2.text === "?") {
|
|
429
543
|
return fail(
|
|
430
544
|
"E_UNPARSABLE",
|
|
431
545
|
"`?` attached to a word is not a marker; a doubt needs whitespace before `?` and after it",
|
|
432
546
|
line,
|
|
433
|
-
|
|
547
|
+
token2
|
|
434
548
|
);
|
|
435
549
|
}
|
|
436
|
-
if (
|
|
437
|
-
return fail("E_UNPARSABLE", "A description needs whitespace before `#`", line,
|
|
550
|
+
if (token2.text === "#") {
|
|
551
|
+
return fail("E_UNPARSABLE", "A description needs whitespace before `#`", line, token2);
|
|
438
552
|
}
|
|
439
|
-
return fail("E_UNPARSABLE", `Unexpected \`${
|
|
553
|
+
return fail("E_UNPARSABLE", `Unexpected \`${token2.text}\` on a ${kind} line`, line, token2);
|
|
440
554
|
}
|
|
441
555
|
|
|
442
556
|
// src/resolve.ts
|
|
@@ -449,10 +563,14 @@ function resolve(doc) {
|
|
|
449
563
|
};
|
|
450
564
|
const classes = out.classes;
|
|
451
565
|
const declared = new Set(classes.map((c) => c.name.text));
|
|
452
|
-
const
|
|
566
|
+
const found = [];
|
|
453
567
|
const undeclared = /* @__PURE__ */ new Map();
|
|
568
|
+
const tree = inheritance(classes);
|
|
454
569
|
const warn = (code, message, at) => {
|
|
455
|
-
|
|
570
|
+
found.push({ severity: "warning", code, message, line: at.line, col: at.col, end: at.end });
|
|
571
|
+
};
|
|
572
|
+
const error = (code, message, at) => {
|
|
573
|
+
found.push({ severity: "error", code, message, line: at.line, col: at.col, end: at.end });
|
|
456
574
|
};
|
|
457
575
|
const reference = (name2) => {
|
|
458
576
|
if (declared.has(name2.text)) return true;
|
|
@@ -473,9 +591,25 @@ function resolve(doc) {
|
|
|
473
591
|
cls.name
|
|
474
592
|
);
|
|
475
593
|
}
|
|
594
|
+
const parent = cls.parent;
|
|
595
|
+
if (parent !== void 0) {
|
|
596
|
+
reference(parent);
|
|
597
|
+
const circle = tree.circle(cls);
|
|
598
|
+
if (circle !== void 0) {
|
|
599
|
+
const through = circle.slice(1).map((member2) => `\`${member2.name.text}\``);
|
|
600
|
+
const path = through.length === 0 ? "" : ` through ${through.join(", ")}`;
|
|
601
|
+
error(
|
|
602
|
+
"E_INHERITANCE_CYCLE",
|
|
603
|
+
`class \`${cls.name.text}\` inherits from itself${path}; this \`<\` is not carried`,
|
|
604
|
+
parent
|
|
605
|
+
);
|
|
606
|
+
cls.parent = void 0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
476
609
|
if (cls.similarTo !== void 0) reference(cls.similarTo);
|
|
477
610
|
const firstField = /* @__PURE__ */ new Map();
|
|
478
|
-
|
|
611
|
+
const inheritedIdentifier = tree.inheritedIdentifier(cls);
|
|
612
|
+
let identifier = inheritedIdentifier?.field;
|
|
479
613
|
for (const field of cls.fields) {
|
|
480
614
|
const earlierField = firstField.get(field.name.text);
|
|
481
615
|
if (earlierField === void 0) firstField.set(field.name.text, field);
|
|
@@ -489,14 +623,23 @@ function resolve(doc) {
|
|
|
489
623
|
if (field.identifier) {
|
|
490
624
|
if (identifier === void 0) identifier = field;
|
|
491
625
|
else {
|
|
626
|
+
const from = identifier === inheritedIdentifier?.field ? ` inherits the identifier \`${identifier.name.text}\` from class \`${inheritedIdentifier.from.name.text}\`, line` : ` already has the identifier \`${identifier.name.text}\` on line`;
|
|
492
627
|
warn(
|
|
493
628
|
"W_MULTIPLE_IDENTIFIERS",
|
|
494
|
-
`\`*\` on \`${field.name.text}\` is ignored: class \`${cls.name.text}
|
|
629
|
+
`\`*\` on \`${field.name.text}\` is ignored: class \`${cls.name.text}\`${from} ${identifier.line}`,
|
|
495
630
|
field.identifierAt ?? field.name
|
|
496
631
|
);
|
|
497
632
|
field.identifier = false;
|
|
498
633
|
}
|
|
499
634
|
}
|
|
635
|
+
const replaced = tree.inherited(cls, field.name.text);
|
|
636
|
+
if (replaced !== void 0 && sameField(field, replaced.field)) {
|
|
637
|
+
warn(
|
|
638
|
+
"W_REDUNDANT_OVERRIDE",
|
|
639
|
+
`field \`${field.name.text}\` is identical to the one inherited from class \`${replaced.from.name.text}\` on line ${replaced.field.line}`,
|
|
640
|
+
field.name
|
|
641
|
+
);
|
|
642
|
+
}
|
|
500
643
|
if (field.type?.kind === "unknown") {
|
|
501
644
|
const word = field.type.name.text;
|
|
502
645
|
const suggestion = closestTypeWord(word);
|
|
@@ -534,8 +677,8 @@ function resolve(doc) {
|
|
|
534
677
|
}
|
|
535
678
|
}
|
|
536
679
|
const present2 = new Set(out.diagnostics.map(key));
|
|
537
|
-
for (const
|
|
538
|
-
if (!present2.has(key(
|
|
680
|
+
for (const d of found) {
|
|
681
|
+
if (!present2.has(key(d))) out.diagnostics.push(d);
|
|
539
682
|
}
|
|
540
683
|
out.diagnostics.sort((a, b) => a.line - b.line);
|
|
541
684
|
out.undeclared = [...undeclared.values()].map((n) => ({ ...n }));
|
|
@@ -566,6 +709,7 @@ function copyField(f) {
|
|
|
566
709
|
}
|
|
567
710
|
function copyClass(c) {
|
|
568
711
|
const out = { ...c, name: copyName(c.name), fields: c.fields.map(copyField) };
|
|
712
|
+
if (c.parent !== void 0) out.parent = copyName(c.parent);
|
|
569
713
|
if (c.system !== void 0) out.system = copyName(c.system);
|
|
570
714
|
if (c.similarTo !== void 0) out.similarTo = copyName(c.similarTo);
|
|
571
715
|
return out;
|
|
@@ -744,6 +888,7 @@ function buildClass(cls, mappingPrefix, enums) {
|
|
|
744
888
|
const { node, fields } = cls;
|
|
745
889
|
const out = {};
|
|
746
890
|
if (node.description !== void 0) out.description = node.description;
|
|
891
|
+
if (node.parent !== void 0) out.is_a = node.parent.text;
|
|
747
892
|
const annotations = {};
|
|
748
893
|
if (node.system !== void 0) annotations.system = node.system.text;
|
|
749
894
|
if (node.note !== void 0) annotations.note = node.note;
|
|
@@ -826,6 +971,7 @@ function toMermaid(doc, opts) {
|
|
|
826
971
|
}
|
|
827
972
|
for (const cls of resolved.classes) {
|
|
828
973
|
const from = cls.name.text;
|
|
974
|
+
if (cls.parent !== void 0) lines.push(` ${cls.parent.text} <|-- ${from}`);
|
|
829
975
|
if (cls.similarTo !== void 0) lines.push(` ${from} ..> ${cls.similarTo.text} : similar`);
|
|
830
976
|
for (const field of cls.fields) {
|
|
831
977
|
const type = field.type;
|
|
@@ -932,7 +1078,7 @@ function toSkiss(doc) {
|
|
|
932
1078
|
`;
|
|
933
1079
|
}
|
|
934
1080
|
function classBlock(cls) {
|
|
935
|
-
const head = cls.name.text + (cls.system === void 0 ? "" : ` @${cls.system.text}`) + (cls.similarTo === void 0 ? "" : ` ~ ${cls.similarTo.text}`);
|
|
1081
|
+
const head = cls.name.text + (cls.parent === void 0 ? "" : ` < ${cls.parent.text}`) + (cls.system === void 0 ? "" : ` @${cls.system.text}`) + (cls.similarTo === void 0 ? "" : ` ~ ${cls.similarTo.text}`);
|
|
936
1082
|
return [withTrailer(head, cls), ...cls.fields.map(fieldLine)].join("\n");
|
|
937
1083
|
}
|
|
938
1084
|
function fieldLine(field) {
|
|
@@ -986,7 +1132,14 @@ var PRIMITIVE_RANGE = {
|
|
|
986
1132
|
uri: "uri",
|
|
987
1133
|
uriorcurie: "uri"
|
|
988
1134
|
};
|
|
989
|
-
var CLASS_KEYS = /* @__PURE__ */ new Set([
|
|
1135
|
+
var CLASS_KEYS = /* @__PURE__ */ new Set([
|
|
1136
|
+
"description",
|
|
1137
|
+
"is_a",
|
|
1138
|
+
"annotations",
|
|
1139
|
+
"close_mappings",
|
|
1140
|
+
"attributes",
|
|
1141
|
+
"slots"
|
|
1142
|
+
]);
|
|
990
1143
|
var SLOT_KEYS = /* @__PURE__ */ new Set(["description", "annotations", "range", "multivalued", "identifier"]);
|
|
991
1144
|
var CLASS_TAGS = /* @__PURE__ */ new Set(["system", "note", "undeclared"]);
|
|
992
1145
|
var SLOT_TAGS = /* @__PURE__ */ new Set(["system", "note", "joins_to"]);
|
|
@@ -1029,7 +1182,7 @@ function fromLinkML(schema) {
|
|
|
1029
1182
|
for (const key2 of Object.keys(classDefs)) {
|
|
1030
1183
|
const names = /* @__PURE__ */ new Map();
|
|
1031
1184
|
const taken = /* @__PURE__ */ new Set();
|
|
1032
|
-
for (const slot of
|
|
1185
|
+
for (const slot of fieldKeysOf(key2)) {
|
|
1033
1186
|
names.set(slot, unique(toFieldName(slot), taken));
|
|
1034
1187
|
}
|
|
1035
1188
|
fieldNames.set(key2, names);
|
|
@@ -1099,6 +1252,12 @@ function fromLinkML(schema) {
|
|
|
1099
1252
|
const note = oneLine(rawNote);
|
|
1100
1253
|
if (note !== void 0) node.note = note;
|
|
1101
1254
|
} else if (rawNote !== void 0) report("annotation", name2, notText("note", rawNote));
|
|
1255
|
+
const parent = stringOf(get(def, "is_a"));
|
|
1256
|
+
if (parent !== void 0) {
|
|
1257
|
+
const parentName = Object.hasOwn(classDefs, parent) ? classNameOf(parent) : toClassName(parent);
|
|
1258
|
+
node.parent = at0(parentName);
|
|
1259
|
+
reference(name2, parent, parentName);
|
|
1260
|
+
}
|
|
1102
1261
|
const mappings = stringList(get(def, "close_mappings"));
|
|
1103
1262
|
const first = mappings[0];
|
|
1104
1263
|
const target = first === void 0 ? "" : localName(first);
|
|
@@ -1121,6 +1280,9 @@ function fromLinkML(schema) {
|
|
|
1121
1280
|
}
|
|
1122
1281
|
}
|
|
1123
1282
|
const block = get(def, other);
|
|
1283
|
+
if (other === "is_a" && present(block) && stringOf(block) === void 0) {
|
|
1284
|
+
report("is_a", name2, `${shown(block)} is not a class name`);
|
|
1285
|
+
}
|
|
1124
1286
|
if (other === "attributes" && present(block) && asRecord(block) === void 0) {
|
|
1125
1287
|
report("attributes", name2, `${shown(block)} is not a map`);
|
|
1126
1288
|
}
|
|
@@ -1135,10 +1297,12 @@ function fromLinkML(schema) {
|
|
|
1135
1297
|
report("slot_usage", name2);
|
|
1136
1298
|
continue;
|
|
1137
1299
|
}
|
|
1300
|
+
const written = slotKeysOf(def);
|
|
1301
|
+
const inherited = inheritedSlots(key2);
|
|
1138
1302
|
for (const slot of Object.keys(usage)) {
|
|
1139
|
-
if (
|
|
1303
|
+
if (written.includes(slot)) {
|
|
1140
1304
|
report("slot_usage", `${name2}.${fieldNameOf(key2, slot)}`);
|
|
1141
|
-
} else {
|
|
1305
|
+
} else if (!inherited.has(slot)) {
|
|
1142
1306
|
report("slot_usage", name2, `\`${slot}\` is inherited and not in the sketch`);
|
|
1143
1307
|
}
|
|
1144
1308
|
}
|
|
@@ -1146,9 +1310,10 @@ function fromLinkML(schema) {
|
|
|
1146
1310
|
}
|
|
1147
1311
|
report(other, name2);
|
|
1148
1312
|
}
|
|
1149
|
-
|
|
1313
|
+
const own = slotKeysOf(def);
|
|
1314
|
+
for (const slot of fieldKeysOf(key2)) {
|
|
1150
1315
|
const attributes = asRecord(get(def, "attributes")) ?? {};
|
|
1151
|
-
const rawSlot = Object.hasOwn(attributes, slot) ? get(attributes, slot) : get(slotDefs, slot);
|
|
1316
|
+
const rawSlot = !own.includes(slot) ? get(asRecord(get(def, "slot_usage")) ?? {}, slot) : Object.hasOwn(attributes, slot) ? get(attributes, slot) : get(slotDefs, slot);
|
|
1152
1317
|
const slotDef = asRecord(rawSlot);
|
|
1153
1318
|
if (slotDef === void 0 && present(rawSlot)) {
|
|
1154
1319
|
report("slot", `${name2}.${fieldNameOf(key2, slot)}`, "is not a slot definition");
|
|
@@ -1285,6 +1450,34 @@ function fromLinkML(schema) {
|
|
|
1285
1450
|
report("narrowed", element, `\`${range}\` is not a Skiss type; it falls back to string`);
|
|
1286
1451
|
return { kind: "unknown", name: at0(word), many };
|
|
1287
1452
|
}
|
|
1453
|
+
function parentKeyOf(classKey) {
|
|
1454
|
+
const def = asRecord(get(classDefs, classKey)) ?? {};
|
|
1455
|
+
const parent = stringOf(get(def, "is_a"));
|
|
1456
|
+
if (parent === void 0 || !Object.hasOwn(classDefs, parent)) return void 0;
|
|
1457
|
+
return parent;
|
|
1458
|
+
}
|
|
1459
|
+
function inheritedSlots(classKey) {
|
|
1460
|
+
const out = /* @__PURE__ */ new Set();
|
|
1461
|
+
const seen = /* @__PURE__ */ new Set([classKey]);
|
|
1462
|
+
let key2 = parentKeyOf(classKey);
|
|
1463
|
+
while (key2 !== void 0 && !seen.has(key2)) {
|
|
1464
|
+
seen.add(key2);
|
|
1465
|
+
for (const slot of slotKeysOf(asRecord(get(classDefs, key2)) ?? {})) out.add(slot);
|
|
1466
|
+
key2 = parentKeyOf(key2);
|
|
1467
|
+
}
|
|
1468
|
+
return out;
|
|
1469
|
+
}
|
|
1470
|
+
function fieldKeysOf(classKey) {
|
|
1471
|
+
const def = asRecord(get(classDefs, classKey)) ?? {};
|
|
1472
|
+
const keys = slotKeysOf(def);
|
|
1473
|
+
const usage = asRecord(get(def, "slot_usage"));
|
|
1474
|
+
if (usage === void 0) return keys;
|
|
1475
|
+
const inherited = inheritedSlots(classKey);
|
|
1476
|
+
for (const slot of Object.keys(usage)) {
|
|
1477
|
+
if (inherited.has(slot) && !keys.includes(slot)) keys.push(slot);
|
|
1478
|
+
}
|
|
1479
|
+
return keys;
|
|
1480
|
+
}
|
|
1288
1481
|
function listedSomewhere(slot) {
|
|
1289
1482
|
for (const key2 of Object.keys(classDefs)) {
|
|
1290
1483
|
const def = asRecord(get(classDefs, key2)) ?? {};
|
|
@@ -1443,6 +1636,204 @@ var nothing = (element, detail) => ({
|
|
|
1443
1636
|
dropped: [{ kind: UNREADABLE, element, detail }]
|
|
1444
1637
|
});
|
|
1445
1638
|
|
|
1639
|
+
// src/highlight.ts
|
|
1640
|
+
var TRAILING_WS = /[ \t]+$/;
|
|
1641
|
+
function tokenizeLine(line) {
|
|
1642
|
+
const text = line.replace(TRAILING_WS, "");
|
|
1643
|
+
const first = text[0];
|
|
1644
|
+
if (first === void 0) {
|
|
1645
|
+
return [];
|
|
1646
|
+
}
|
|
1647
|
+
if (first === "#") {
|
|
1648
|
+
return [{ kind: "comment", from: 0, to: text.length }];
|
|
1649
|
+
}
|
|
1650
|
+
const { head, trailers } = splitTrailer2(text);
|
|
1651
|
+
return [...headTokens(head, isWs(first)), ...trailers];
|
|
1652
|
+
}
|
|
1653
|
+
function splitTrailer2(text) {
|
|
1654
|
+
let start = -1;
|
|
1655
|
+
for (let i = 1; i < text.length; i++) {
|
|
1656
|
+
if (!isWs(text[i - 1])) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (text[i] === "#" || isDoubtMarker(text, i)) {
|
|
1660
|
+
start = i;
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (start < 0) {
|
|
1665
|
+
return { head: text, trailers: [] };
|
|
1666
|
+
}
|
|
1667
|
+
const head = text.slice(0, start);
|
|
1668
|
+
if (text[start] === "?") {
|
|
1669
|
+
return { head, trailers: [{ kind: "doubt", from: start, to: text.length }] };
|
|
1670
|
+
}
|
|
1671
|
+
let doubt = -1;
|
|
1672
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
1673
|
+
if (isDoubtMarker(text, i)) {
|
|
1674
|
+
doubt = i;
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (doubt < 0) {
|
|
1679
|
+
return { head, trailers: [{ kind: "description", from: start, to: text.length }] };
|
|
1680
|
+
}
|
|
1681
|
+
return {
|
|
1682
|
+
head,
|
|
1683
|
+
trailers: [
|
|
1684
|
+
// The whitespace between the two belongs to neither.
|
|
1685
|
+
{
|
|
1686
|
+
kind: "description",
|
|
1687
|
+
from: start,
|
|
1688
|
+
to: text.slice(0, doubt).replace(TRAILING_WS, "").length
|
|
1689
|
+
},
|
|
1690
|
+
{ kind: "doubt", from: doubt, to: text.length }
|
|
1691
|
+
]
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function scan(head) {
|
|
1695
|
+
const raws = [];
|
|
1696
|
+
let i = 0;
|
|
1697
|
+
while (i < head.length) {
|
|
1698
|
+
const c = head[i] ?? "";
|
|
1699
|
+
if (isWs(c)) {
|
|
1700
|
+
i += 1;
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const word = wordAt(head, i);
|
|
1704
|
+
if (word !== void 0) {
|
|
1705
|
+
raws.push({ text: word, from: i, to: i + word.length, word: true });
|
|
1706
|
+
i += word.length;
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
if (c === "[" && head[i + 1] === "]") {
|
|
1710
|
+
raws.push({ text: "[]", from: i, to: i + 2, word: false });
|
|
1711
|
+
i += 2;
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
const ch = String.fromCodePoint(head.codePointAt(i) ?? 0);
|
|
1715
|
+
raws.push({ text: ch, from: i, to: i + ch.length, word: false });
|
|
1716
|
+
i += ch.length;
|
|
1717
|
+
}
|
|
1718
|
+
return raws;
|
|
1719
|
+
}
|
|
1720
|
+
var token = (kind, raw) => ({ kind, from: raw.from, to: raw.to });
|
|
1721
|
+
function headTokens(head, field) {
|
|
1722
|
+
const raws = scan(head);
|
|
1723
|
+
const tokens = [];
|
|
1724
|
+
let i = 0;
|
|
1725
|
+
const name2 = raws[0];
|
|
1726
|
+
if (name2?.word === true && (field ? FIELD_NAME : CLASS_NAME).test(name2.text)) {
|
|
1727
|
+
tokens.push(token(field ? "field" : "class", name2));
|
|
1728
|
+
i = 1;
|
|
1729
|
+
}
|
|
1730
|
+
while (i < raws.length) {
|
|
1731
|
+
const raw = raws[i];
|
|
1732
|
+
i += 1;
|
|
1733
|
+
if (raw === void 0 || raw.word) {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
switch (raw.text) {
|
|
1737
|
+
case "*":
|
|
1738
|
+
tokens.push(token("marker", raw));
|
|
1739
|
+
break;
|
|
1740
|
+
case ":":
|
|
1741
|
+
tokens.push(token("operator", raw));
|
|
1742
|
+
i = typeTokens(raws, i, tokens);
|
|
1743
|
+
break;
|
|
1744
|
+
case "@":
|
|
1745
|
+
tokens.push(token("operator", raw));
|
|
1746
|
+
i = nameToken(raws, i, tokens, "system", SYSTEM_NAME);
|
|
1747
|
+
break;
|
|
1748
|
+
case "~":
|
|
1749
|
+
case "<":
|
|
1750
|
+
tokens.push(token("operator", raw));
|
|
1751
|
+
i = nameToken(raws, i, tokens, "class", CLASS_NAME);
|
|
1752
|
+
break;
|
|
1753
|
+
case "=":
|
|
1754
|
+
tokens.push(token("operator", raw));
|
|
1755
|
+
i = joinTokens(raws, i, tokens);
|
|
1756
|
+
break;
|
|
1757
|
+
case "|":
|
|
1758
|
+
case "[]":
|
|
1759
|
+
tokens.push(token("operator", raw));
|
|
1760
|
+
break;
|
|
1761
|
+
default:
|
|
1762
|
+
break;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return tokens;
|
|
1766
|
+
}
|
|
1767
|
+
function nameToken(raws, i, tokens, kind, shape) {
|
|
1768
|
+
const raw = raws[i];
|
|
1769
|
+
if (raw === void 0 || !raw.word) {
|
|
1770
|
+
return i;
|
|
1771
|
+
}
|
|
1772
|
+
if (shape.test(raw.text)) {
|
|
1773
|
+
tokens.push(token(kind, raw));
|
|
1774
|
+
}
|
|
1775
|
+
return i + 1;
|
|
1776
|
+
}
|
|
1777
|
+
function joinTokens(raws, start, tokens) {
|
|
1778
|
+
let i = nameToken(raws, start, tokens, "class", CLASS_NAME);
|
|
1779
|
+
const dot = raws[i];
|
|
1780
|
+
if (dot === void 0 || dot.text !== "." || dot.word) {
|
|
1781
|
+
return i;
|
|
1782
|
+
}
|
|
1783
|
+
tokens.push(token("operator", dot));
|
|
1784
|
+
i += 1;
|
|
1785
|
+
return nameToken(raws, i, tokens, "field", FIELD_NAME);
|
|
1786
|
+
}
|
|
1787
|
+
function typeTokens(raws, start, tokens) {
|
|
1788
|
+
const first = raws[start];
|
|
1789
|
+
if (first === void 0 || !first.word) {
|
|
1790
|
+
return start;
|
|
1791
|
+
}
|
|
1792
|
+
const words = [first];
|
|
1793
|
+
const operators = [];
|
|
1794
|
+
let pipes = 0;
|
|
1795
|
+
let i = start + 1;
|
|
1796
|
+
while (raws[i]?.text === "|") {
|
|
1797
|
+
const pipe = raws[i];
|
|
1798
|
+
if (pipe === void 0) {
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
operators.push(pipe);
|
|
1802
|
+
pipes += 1;
|
|
1803
|
+
i += 1;
|
|
1804
|
+
const value = raws[i];
|
|
1805
|
+
if (value === void 0 || !value.word) {
|
|
1806
|
+
break;
|
|
1807
|
+
}
|
|
1808
|
+
words.push(value);
|
|
1809
|
+
i += 1;
|
|
1810
|
+
}
|
|
1811
|
+
const many = raws[i];
|
|
1812
|
+
if (many !== void 0 && many.text === "[]" && !many.word) {
|
|
1813
|
+
operators.push(many);
|
|
1814
|
+
i += 1;
|
|
1815
|
+
}
|
|
1816
|
+
const typed = pipes > 0 ? words.filter((word) => ENUM_VALUE.test(word.text)).map((word) => token("enum", word)) : nameTypeTokens(first);
|
|
1817
|
+
tokens.push(
|
|
1818
|
+
...[...typed, ...operators.map((raw) => token("operator", raw))].sort(
|
|
1819
|
+
(a, b) => a.from - b.from
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1822
|
+
return i;
|
|
1823
|
+
}
|
|
1824
|
+
function nameTypeTokens(word) {
|
|
1825
|
+
if (primitiveFor(word.text) !== void 0) {
|
|
1826
|
+
return [token("primitive", word)];
|
|
1827
|
+
}
|
|
1828
|
+
if (CLASS_NAME.test(word.text)) {
|
|
1829
|
+
return [token("class", word)];
|
|
1830
|
+
}
|
|
1831
|
+
if (/^[a-z]/.test(word.text)) {
|
|
1832
|
+
return [token("type", word)];
|
|
1833
|
+
}
|
|
1834
|
+
return [];
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1446
1837
|
// src/import.ts
|
|
1447
1838
|
import { parse as parseYAML } from "yaml";
|
|
1448
1839
|
function importLinkML(text) {
|
|
@@ -1467,7 +1858,7 @@ function notYAML(error) {
|
|
|
1467
1858
|
}
|
|
1468
1859
|
|
|
1469
1860
|
// src/index.ts
|
|
1470
|
-
var VERSION = "0.
|
|
1861
|
+
var VERSION = "0.6.0";
|
|
1471
1862
|
export {
|
|
1472
1863
|
VERSION,
|
|
1473
1864
|
compile,
|
|
@@ -1480,6 +1871,7 @@ export {
|
|
|
1480
1871
|
serialize,
|
|
1481
1872
|
toLinkML,
|
|
1482
1873
|
toMermaid,
|
|
1483
|
-
toSkiss
|
|
1874
|
+
toSkiss,
|
|
1875
|
+
tokenizeLine
|
|
1484
1876
|
};
|
|
1485
1877
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eriknaslund/skiss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A text notation for sketching data models, compiled to LinkML",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@biomejs/biome": "^2.5.13",
|
|
44
|
-
"@
|
|
44
|
+
"@mermaid-js/mermaid-cli": "^11.17.0",
|
|
45
|
+
"@types/node": "^26.6.1",
|
|
45
46
|
"jsdom": "^30.0.1",
|
|
46
47
|
"mermaid": "^12.0.0",
|
|
47
48
|
"tsup": "^8.5.1",
|