@oml/language 0.18.1 → 0.19.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/out/oml/generated/ast.d.ts +178 -8
- package/out/oml/generated/ast.js +140 -0
- package/out/oml/generated/ast.js.map +1 -1
- package/out/oml/generated/grammar.js +1073 -375
- package/out/oml/generated/grammar.js.map +1 -1
- package/out/oml/oml-index.d.ts +35 -0
- package/out/oml/oml-index.js +185 -1
- package/out/oml/oml-index.js.map +1 -1
- package/out/oml/oml-serializer.js +14 -9
- package/out/oml/oml-serializer.js.map +1 -1
- package/out/oml/oml-update.js +60 -9
- package/out/oml/oml-update.js.map +1 -1
- package/out/oml/oml-validator.d.ts +9 -1
- package/out/oml/oml-validator.js +224 -3
- package/out/oml/oml-validator.js.map +1 -1
- package/package.json +1 -1
- package/src/oml/generated/ast.ts +198 -7
- package/src/oml/generated/grammar.ts +1073 -375
- package/src/oml/oml-index.ts +208 -1
- package/src/oml/oml-serializer.ts +15 -9
- package/src/oml/oml-update.ts +70 -9
- package/src/oml/oml-validator.ts +232 -5
- package/src/oml/oml.langium +39 -7
package/src/oml/oml-validator.ts
CHANGED
|
@@ -11,7 +11,40 @@ import {
|
|
|
11
11
|
getMemberName,
|
|
12
12
|
getNamedElementName,
|
|
13
13
|
} from './oml-utils.js';
|
|
14
|
-
import type { EntityEquivalenceAxiom, ForwardRelation, Import, Member, OmlAstType, Ontology, PropertyEquivalenceAxiom, QuotedLiteral, RelationEntity, RelationInstance, ReverseRelation, Rule, Scalar, ScalarEquivalenceAxiom, TypeAssertion } from './generated/ast.js';
|
|
14
|
+
import type { EntityEquivalenceAxiom, ForwardRelation, Import, Member, OmlAstType, Ontology, PropertyEquivalenceAxiom, PropertyRangeRestrictionAxiom, PropertyValueAssertion, Quantity, QuantityProperty, QuotedLiteral, RelationEntity, RelationInstance, ReverseRelation, Rule, Scalar, ScalarEquivalenceAxiom, TypeAssertion, Unit } from './generated/ast.js';
|
|
15
|
+
|
|
16
|
+
type DimensionVector = { L: number; M: number; T: number; I: number; H: number; N: number; J: number };
|
|
17
|
+
|
|
18
|
+
function emptyDimension(): DimensionVector {
|
|
19
|
+
return { L: 0, M: 0, T: 0, I: 0, H: 0, N: 0, J: 0 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseDimension(input: string): { vector?: DimensionVector; error?: string } {
|
|
23
|
+
const trimmed = (input ?? '').trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
return { vector: emptyDimension() };
|
|
26
|
+
}
|
|
27
|
+
const tokens = trimmed.split(/\s+/);
|
|
28
|
+
const result = emptyDimension();
|
|
29
|
+
const seen = new Set<string>();
|
|
30
|
+
for (const tok of tokens) {
|
|
31
|
+
const m = /^([LMTIHNJ])(-?\d+)$/.exec(tok);
|
|
32
|
+
if (!m) {
|
|
33
|
+
return { error: `Invalid dimension token '${tok}'. Expected axis (L|M|T|I|H|N|J) followed by a signed integer.` };
|
|
34
|
+
}
|
|
35
|
+
const axis = m[1];
|
|
36
|
+
if (seen.has(axis)) {
|
|
37
|
+
return { error: `Dimension axis '${axis}' appears more than once.` };
|
|
38
|
+
}
|
|
39
|
+
seen.add(axis);
|
|
40
|
+
(result as any)[axis] = parseInt(m[2], 10);
|
|
41
|
+
}
|
|
42
|
+
return { vector: result };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function dimensionsEqual(a: DimensionVector, b: DimensionVector): boolean {
|
|
46
|
+
return a.L === b.L && a.M === b.M && a.T === b.T && a.I === b.I && a.H === b.H && a.N === b.N && a.J === b.J;
|
|
47
|
+
}
|
|
15
48
|
|
|
16
49
|
interface OntologyValidationContext {
|
|
17
50
|
version: number;
|
|
@@ -60,7 +93,7 @@ export function registerValidationChecks(services: OmlServices) {
|
|
|
60
93
|
InstanceEnumerationAxiom: [],
|
|
61
94
|
LiteralEnumerationAxiom: [],
|
|
62
95
|
PropertyRestrictionAxiom: [],
|
|
63
|
-
PropertyRangeRestrictionAxiom: [],
|
|
96
|
+
PropertyRangeRestrictionAxiom: [validator.checkInvalidQuantityPropertyRangeRestriction],
|
|
64
97
|
PropertyCardinalityRestrictionAxiom: [],
|
|
65
98
|
PropertyValueRestrictionAxiom: [],
|
|
66
99
|
PropertySelfRestrictionAxiom: [],
|
|
@@ -75,7 +108,7 @@ export function registerValidationChecks(services: OmlServices) {
|
|
|
75
108
|
// Assertion types
|
|
76
109
|
Assertion: [],
|
|
77
110
|
TypeAssertion: [validator.checkInvalidInstanceType],
|
|
78
|
-
PropertyValueAssertion: [],
|
|
111
|
+
PropertyValueAssertion: [validator.checkInvalidPropertyValueAssertion],
|
|
79
112
|
// Literal types
|
|
80
113
|
Literal: [],
|
|
81
114
|
IntegerLiteral: [],
|
|
@@ -122,6 +155,10 @@ export function registerValidationChecks(services: OmlServices) {
|
|
|
122
155
|
Aspect: [],
|
|
123
156
|
Concept: [],
|
|
124
157
|
RelationEntity: [validator.checkInvalidRelationEntity],
|
|
158
|
+
// Quantity types
|
|
159
|
+
Quantity: [validator.checkQuantityDimensionFormat],
|
|
160
|
+
Unit: [validator.checkUnitKindsDimensionalConsistency, validator.checkUnitSymbolUniqueness],
|
|
161
|
+
QuantityProperty: [validator.checkQuantityPropertyCardinality],
|
|
125
162
|
};
|
|
126
163
|
registry.register(checks, validator);
|
|
127
164
|
}
|
|
@@ -200,7 +237,7 @@ export class OmlValidator {
|
|
|
200
237
|
|
|
201
238
|
const context = this.getOntologyValidationContext(ontology);
|
|
202
239
|
if (!context?.usedPrefixes.has(prefix)) {
|
|
203
|
-
accept('warning', `
|
|
240
|
+
accept('warning', `Prefix '${ownedImport.prefix}' is declared but not used`, {
|
|
204
241
|
node: ownedImport
|
|
205
242
|
});
|
|
206
243
|
}
|
|
@@ -533,7 +570,182 @@ export class OmlValidator {
|
|
|
533
570
|
}
|
|
534
571
|
}
|
|
535
572
|
|
|
536
|
-
|
|
573
|
+
checkInvalidPropertyValueAssertion(assertion: PropertyValueAssertion, accept: ValidationAcceptor): void {
|
|
574
|
+
const property = assertion.property?.ref as any;
|
|
575
|
+
if (!property) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const propertyKind = this.getPropertyKind(property);
|
|
580
|
+
const literals = assertion.literalValues ?? [];
|
|
581
|
+
|
|
582
|
+
if (propertyKind === 'scalar property') {
|
|
583
|
+
const hasObjectValues = (assertion.referencedValues?.length ?? 0) > 0
|
|
584
|
+
|| (assertion.containedValues?.length ?? 0) > 0;
|
|
585
|
+
if (hasObjectValues) {
|
|
586
|
+
accept('error', `Scalar property ${this.getAbbreviatedIri(property)} cannot have instance values`, {
|
|
587
|
+
node: assertion,
|
|
588
|
+
property: (assertion.referencedValues?.length ?? 0) > 0 ? 'referencedValues' : 'containedValues'
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
for (let i = 0; i < literals.length; i++) {
|
|
592
|
+
if (this.literalUnit(literals[i])) {
|
|
593
|
+
accept('error', `Unit suffix is only allowed on quantity property values`, {
|
|
594
|
+
node: assertion,
|
|
595
|
+
property: 'literalValues',
|
|
596
|
+
index: i
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (propertyKind === 'relation') {
|
|
604
|
+
if (literals.length > 0) {
|
|
605
|
+
accept('error', `Relation ${this.getAbbreviatedIri(property)} cannot have literal values`, {
|
|
606
|
+
node: assertion,
|
|
607
|
+
property: 'literalValues'
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (propertyKind === 'quantity property') {
|
|
614
|
+
const hasObjectValues = (assertion.referencedValues?.length ?? 0) > 0
|
|
615
|
+
|| (assertion.containedValues?.length ?? 0) > 0;
|
|
616
|
+
if (hasObjectValues) {
|
|
617
|
+
accept('error', `Quantity property ${this.getAbbreviatedIri(property)} cannot have instance values`, {
|
|
618
|
+
node: assertion,
|
|
619
|
+
property: (assertion.referencedValues?.length ?? 0) > 0 ? 'referencedValues' : 'containedValues'
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
const propKinds = new Set<any>((((property as any).kind ?? []) as any[])
|
|
623
|
+
.map((r: any) => r?.ref)
|
|
624
|
+
.filter(Boolean));
|
|
625
|
+
for (let i = 0; i < literals.length; i++) {
|
|
626
|
+
const lit: any = literals[i];
|
|
627
|
+
if (!this.isNumericLiteral(lit)) {
|
|
628
|
+
accept('error', `Quantity property ${this.getAbbreviatedIri(property)} requires a numeric literal`, {
|
|
629
|
+
node: assertion,
|
|
630
|
+
property: 'literalValues',
|
|
631
|
+
index: i
|
|
632
|
+
});
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
const unit = lit?.unit?.ref;
|
|
636
|
+
if (!unit) {
|
|
637
|
+
if (lit?.unit?.$refText) {
|
|
638
|
+
// Reference present but unresolved; let the linker handle it.
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
accept('error', `Quantity property ${this.getAbbreviatedIri(property)} requires a unit suffix (e.g., '5^^unit:Metre')`, {
|
|
642
|
+
node: assertion,
|
|
643
|
+
property: 'literalValues',
|
|
644
|
+
index: i
|
|
645
|
+
});
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (propKinds.size === 0) continue;
|
|
649
|
+
const unitKinds: any[] = ((unit.kinds ?? []) as any[])
|
|
650
|
+
.map((r: any) => r?.ref)
|
|
651
|
+
.filter(Boolean);
|
|
652
|
+
if (unitKinds.length === 0) continue;
|
|
653
|
+
const overlap = unitKinds.some((k: any) => propKinds.has(k));
|
|
654
|
+
if (!overlap) {
|
|
655
|
+
accept('error', `Unit ${this.getAbbreviatedIri(unit)} is not applicable to any quantity kind of property ${this.getAbbreviatedIri(property)}`, {
|
|
656
|
+
node: assertion,
|
|
657
|
+
property: 'literalValues',
|
|
658
|
+
index: i
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
checkQuantityDimensionFormat(quantity: Quantity, accept: ValidationAcceptor): void {
|
|
666
|
+
const dim = quantity.dimension;
|
|
667
|
+
if (!dim) return;
|
|
668
|
+
const parsed = parseDimension(dim);
|
|
669
|
+
if (parsed.error) {
|
|
670
|
+
accept('error', parsed.error, {
|
|
671
|
+
node: quantity,
|
|
672
|
+
property: 'dimension'
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
checkUnitKindsDimensionalConsistency(unit: Unit, accept: ValidationAcceptor): void {
|
|
678
|
+
const kindRefs = ((unit.kinds ?? []) as any[]);
|
|
679
|
+
const kinds = kindRefs.map((r: any) => r?.ref).filter(Boolean) as any[];
|
|
680
|
+
if (kinds.length <= 1) return;
|
|
681
|
+
const vectors: DimensionVector[] = [];
|
|
682
|
+
for (const k of kinds) {
|
|
683
|
+
const dim = typeof k?.dimension === 'string' ? k.dimension : '';
|
|
684
|
+
if (!dim) continue;
|
|
685
|
+
const parsed = parseDimension(dim);
|
|
686
|
+
if (parsed.vector) vectors.push(parsed.vector);
|
|
687
|
+
}
|
|
688
|
+
if (vectors.length <= 1) return;
|
|
689
|
+
const ref = vectors[0];
|
|
690
|
+
for (let i = 1; i < vectors.length; i++) {
|
|
691
|
+
if (!dimensionsEqual(ref, vectors[i])) {
|
|
692
|
+
accept('error', `Unit ${this.getAbbreviatedIri(unit)} declares quantity kinds with inconsistent dimensions`, {
|
|
693
|
+
node: unit,
|
|
694
|
+
property: 'kinds'
|
|
695
|
+
});
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
checkUnitSymbolUniqueness(unit: Unit, accept: ValidationAcceptor): void {
|
|
702
|
+
const ontology: any = findOwningOntologyNode(unit);
|
|
703
|
+
if (!ontology || ontology.$type !== 'Vocabulary') return;
|
|
704
|
+
const symbols = ((unit as any).symbol ?? []) as string[];
|
|
705
|
+
if (symbols.length === 0) return;
|
|
706
|
+
if (symbols.length > 1) {
|
|
707
|
+
accept('error', `Unit ${this.getAbbreviatedIri(unit)} declares more than one symbol; only one is allowed`, {
|
|
708
|
+
node: unit,
|
|
709
|
+
property: 'symbol' as any,
|
|
710
|
+
index: 1
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const statements: any[] = ontology.ownedStatements ?? [];
|
|
714
|
+
const otherUnits = statements.filter((s: any) => s && s !== unit && s.$type === 'Unit');
|
|
715
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
716
|
+
const sym = symbols[i];
|
|
717
|
+
const conflict = otherUnits.find((u: any) => ((u.symbol ?? []) as string[]).includes(sym));
|
|
718
|
+
if (conflict) {
|
|
719
|
+
accept('error', `Unit symbol "${sym}" is already used by unit ${this.getAbbreviatedIri(conflict)}`, {
|
|
720
|
+
node: unit,
|
|
721
|
+
property: 'symbol' as any,
|
|
722
|
+
index: i
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
checkQuantityPropertyCardinality(prop: QuantityProperty, accept: ValidationAcceptor): void {
|
|
729
|
+
const kinds = (((prop as any).kind ?? []) as any[]);
|
|
730
|
+
if (kinds.length > 1) {
|
|
731
|
+
accept('error', `Quantity property ${this.getAbbreviatedIri(prop)} declares more than one quantity; only one is allowed`, {
|
|
732
|
+
node: prop,
|
|
733
|
+
property: 'kind' as any,
|
|
734
|
+
index: 1
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
checkInvalidQuantityPropertyRangeRestriction(axiom: PropertyRangeRestrictionAxiom, accept: ValidationAcceptor): void {
|
|
740
|
+
const prop = axiom.property?.ref as any;
|
|
741
|
+
if (prop?.$type === 'QuantityProperty') {
|
|
742
|
+
accept('warning', `Range restriction on quantity property ${this.getAbbreviatedIri(prop)} is not meaningful; use a value restriction instead.`, {
|
|
743
|
+
node: axiom
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private getPropertyKind(property: any): 'annotation property' | 'scalar property' | 'relation' | 'quantity property' | undefined {
|
|
537
749
|
const type = property?.$type;
|
|
538
750
|
if (type === 'AnnotationProperty') {
|
|
539
751
|
return 'annotation property';
|
|
@@ -544,9 +756,24 @@ export class OmlValidator {
|
|
|
544
756
|
if (type === 'UnreifiedRelation' || type === 'ForwardRelation' || type === 'ReverseRelation') {
|
|
545
757
|
return 'relation';
|
|
546
758
|
}
|
|
759
|
+
if (type === 'QuantityProperty') {
|
|
760
|
+
return 'quantity property';
|
|
761
|
+
}
|
|
547
762
|
return undefined;
|
|
548
763
|
}
|
|
549
764
|
|
|
765
|
+
private isNumericLiteral(node: any): boolean {
|
|
766
|
+
const t = node?.$type;
|
|
767
|
+
return t === 'IntegerLiteral' || t === 'DecimalLiteral' || t === 'DoubleLiteral';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private literalUnit(node: any): any {
|
|
771
|
+
if (!this.isNumericLiteral(node)) return undefined;
|
|
772
|
+
const ref = node?.unit;
|
|
773
|
+
if (!ref) return undefined;
|
|
774
|
+
return ref?.ref ?? (ref?.$refText ? ref : undefined);
|
|
775
|
+
}
|
|
776
|
+
|
|
550
777
|
private isStandardScalar(scalar: any): boolean {
|
|
551
778
|
const ontology = findOwningOntologyNode(scalar);
|
|
552
779
|
const namespace = typeof ontology?.namespace === 'string' ? ontology.namespace : '';
|
package/src/oml/oml.langium
CHANGED
|
@@ -148,7 +148,9 @@ DescriptionMember:
|
|
|
148
148
|
VocabularyStatement:
|
|
149
149
|
Rule |
|
|
150
150
|
BuiltIn |
|
|
151
|
-
SpecializableTerm
|
|
151
|
+
SpecializableTerm |
|
|
152
|
+
Quantity |
|
|
153
|
+
Unit;
|
|
152
154
|
|
|
153
155
|
DescriptionStatement:
|
|
154
156
|
NamedInstance;
|
|
@@ -160,7 +162,9 @@ DescriptionStatement:
|
|
|
160
162
|
Term:
|
|
161
163
|
SpecializableTerm |
|
|
162
164
|
RelationBase |
|
|
163
|
-
Property
|
|
165
|
+
Property |
|
|
166
|
+
Quantity |
|
|
167
|
+
Unit;
|
|
164
168
|
|
|
165
169
|
Rule:
|
|
166
170
|
ownedAnnotations+=Annotation*
|
|
@@ -200,7 +204,8 @@ RelationBase:
|
|
|
200
204
|
SpecializableProperty:
|
|
201
205
|
AnnotationProperty |
|
|
202
206
|
ScalarProperty |
|
|
203
|
-
UnreifiedRelation
|
|
207
|
+
UnreifiedRelation |
|
|
208
|
+
QuantityProperty;
|
|
204
209
|
|
|
205
210
|
////////////////////////////////////////
|
|
206
211
|
// Types
|
|
@@ -283,7 +288,8 @@ AnnotationProperty:
|
|
|
283
288
|
|
|
284
289
|
SemanticProperty:
|
|
285
290
|
ScalarProperty |
|
|
286
|
-
Relation
|
|
291
|
+
Relation |
|
|
292
|
+
QuantityProperty;
|
|
287
293
|
|
|
288
294
|
fragment PropertySpecialization:
|
|
289
295
|
'<' ownedSpecializations+=SpecializationAxiom (',' ownedSpecializations+=SpecializationAxiom)*;
|
|
@@ -335,6 +341,32 @@ UnreifiedRelation:
|
|
|
335
341
|
(transitive?='transitive'))
|
|
336
342
|
']')? (PropertySpecialization)? (PropertyEquivalence)?;
|
|
337
343
|
|
|
344
|
+
////////////////////////////////////////
|
|
345
|
+
// Quantities and Units
|
|
346
|
+
////////////////////////////////////////
|
|
347
|
+
|
|
348
|
+
Quantity:
|
|
349
|
+
ownedAnnotations+=Annotation*
|
|
350
|
+
('quantity' name=ID | 'ref' 'quantity' ref=[Quantity:Ref]) ('['
|
|
351
|
+
('dimension' dimension=STRING)?
|
|
352
|
+
']')?;
|
|
353
|
+
|
|
354
|
+
Unit:
|
|
355
|
+
ownedAnnotations+=Annotation*
|
|
356
|
+
('unit' name=ID | 'ref' 'unit' ref=[Unit:Ref]) ('['
|
|
357
|
+
(('measures' kinds+=[Quantity:Ref] (',' kinds+=[Quantity:Ref])*) |
|
|
358
|
+
('symbol' symbol+=STRING) |
|
|
359
|
+
('multiplier' multiplier+=Decimal))*
|
|
360
|
+
']')?;
|
|
361
|
+
|
|
362
|
+
QuantityProperty:
|
|
363
|
+
ownedAnnotations+=Annotation*
|
|
364
|
+
('quantity' 'property' name=ID | 'ref' 'quantity' 'property' ref=[QuantityProperty:Ref]) ('['
|
|
365
|
+
(('domain' domains+=[Entity:Ref] (',' domains+=[Entity:Ref])*) |
|
|
366
|
+
('quantity' kind+=[Quantity:Ref]))*
|
|
367
|
+
(functional?='functional')?
|
|
368
|
+
']')? (PropertySpecialization)? (PropertyEquivalence)?;
|
|
369
|
+
|
|
338
370
|
////////////////////////////////////////
|
|
339
371
|
// Description Members and Instances
|
|
340
372
|
////////////////////////////////////////
|
|
@@ -501,13 +533,13 @@ DifferentFromPredicate:
|
|
|
501
533
|
////////////////////////////////////////
|
|
502
534
|
|
|
503
535
|
IntegerLiteral:
|
|
504
|
-
value=Integer
|
|
536
|
+
value=Integer ('^^' unit=[Unit:Ref])?;
|
|
505
537
|
|
|
506
538
|
DecimalLiteral:
|
|
507
|
-
value=Decimal
|
|
539
|
+
value=Decimal ('^^' unit=[Unit:Ref])?;
|
|
508
540
|
|
|
509
541
|
DoubleLiteral:
|
|
510
|
-
value=Double
|
|
542
|
+
value=Double ('^^' unit=[Unit:Ref])?;
|
|
511
543
|
|
|
512
544
|
BooleanLiteral:
|
|
513
545
|
value=Boolean;
|