Skip to content

Commit 25f7feb

Browse files
committed
Ensure we can use Lionweb nodes as parser traces. Improve coverage of ECore support functions.
1 parent c3f8652 commit 25f7feb

File tree

8 files changed

+78
-42
lines changed

8 files changed

+78
-42
lines changed

src/interop/ecore.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ class ReferencesTracker {
552552
}
553553
}
554554

555-
getReferredObject(uri: string) {
555+
getReferredObject(uri?: string) {
556556
if (uri === undefined) {
557557
return undefined;
558558
}
@@ -621,7 +621,7 @@ export function findEClass(name: string, resource: ECore.Resource): ECore.EClass
621621
const packageName = name.substring(0, index);
622622
const ePackage = ECore.EPackage.Registry.getEPackage(packageName);
623623
if(!ePackage) {
624-
throw new Error("Package not found: " + packageName+ " while loading for class " + name);
624+
throw new Error("Package not found: " + packageName + " while loading class " + name);
625625
}
626626
const className = name.substring(name.lastIndexOf("/") + 1);
627627
if (ePackage.get("nsURI") == KOLASU_URI_V1) {

src/interop/lionweb.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,6 @@ export class LionwebNode extends NodeAdapter {
165165
return undefined;
166166
}
167167

168-
getRole(): string | undefined {
169-
return undefined;
170-
}
171-
172168
isDeclaration(): boolean {
173169
return false;
174170
}
@@ -180,4 +176,8 @@ export class LionwebNode extends NodeAdapter {
180176
isStatement(): boolean {
181177
return false;
182178
}
179+
180+
equals(other: NodeAdapter | undefined): boolean {
181+
return other instanceof LionwebNode && other.lwnode == this.lwnode;
182+
}
183183
}

src/interop/strumenta-playground.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class ParserNode extends TraceNode {
9292
}
9393

9494
getChildren(role?: string): ParserNode[] {
95-
return this.wrappedNode.getChildren(role).map((c) => new ParserNode(c, this, this.trace));
95+
return this.nodeAdapter.getChildren(role).map((c) => new ParserNode(c, this, this.trace));
9696
}
9797

9898
get children(): Node[] {
@@ -165,7 +165,7 @@ abstract class AbstractTranspilationTrace {
165165
protected constructor(protected wrappedNode: NodeAdapter) {}
166166

167167
getDestinationNodes(sourceNode: SourceNode): TargetNode[] {
168-
return this.sourceToTarget.get(sourceNode.wrappedNode.getId()) || [];
168+
return this.sourceToTarget.get(sourceNode.nodeAdapter.getId()) || [];
169169
}
170170

171171
protected examineTargetNode(
@@ -294,7 +294,7 @@ export class SourceNode extends TraceNode {
294294
}
295295

296296
getChildren(role?: string): SourceNode[] {
297-
return this.wrappedNode.getChildren(role).map((c) => new SourceNode(c, this.trace, this.file).withParent(this));
297+
return this.nodeAdapter.getChildren(role).map((c) => new SourceNode(c, this.trace, this.file).withParent(this));
298298
}
299299

300300
get children(): Node[] {
@@ -313,7 +313,7 @@ export class TargetNode extends TraceNode {
313313
}
314314

315315
getPosition(): Position | undefined {
316-
return this.wrappedNode.getPosition("destination");
316+
return this.nodeAdapter.getPosition("destination");
317317
}
318318

319319
getDestination(): Position | undefined {
@@ -327,8 +327,8 @@ export class TargetNode extends TraceNode {
327327
getSourceNode(): SourceNode | undefined {
328328
if (!this.origin) {
329329
// TODO
330-
if (this.wrappedNode instanceof ECoreNode) {
331-
let rawOrigin = this.wrappedNode.eo.get("origin");
330+
if (this.nodeAdapter instanceof ECoreNode) {
331+
let rawOrigin = this.nodeAdapter.eo.get("origin");
332332
if (rawOrigin?.eClass == THE_NODE_ORIGIN_ECLASS) {
333333
rawOrigin = rawOrigin.get("node");
334334
}
@@ -352,7 +352,7 @@ export class TargetNode extends TraceNode {
352352
}
353353

354354
getChildren(role?: string): TargetNode[] {
355-
return this.wrappedNode.getChildren(role).map((c) => new TargetNode(c, this.trace, this.file));
355+
return this.nodeAdapter.getChildren(role).map((c) => new TargetNode(c, this.trace, this.file));
356356
}
357357

358358
get children(): Node[] {

src/mapping.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ export class ParseTreeToASTTransformer extends ASTTransformer {
2525

2626
getSource(node: Node, source: any): any {
2727
const origin = node.origin;
28-
if (origin instanceof ParseTreeOrigin)
28+
if (origin instanceof ParseTreeOrigin) {
2929
return origin.parseTree;
30-
else
30+
} else {
3131
return source;
32+
}
3233
}
3334

3435
asOrigin(source: any): Origin | undefined {
35-
if (source instanceof ParserRuleContext || source instanceof TerminalNode)
36+
if (source instanceof ParserRuleContext || source instanceof TerminalNode) {
3637
return new ParseTreeOrigin(source);
37-
else
38+
} else {
3839
return undefined;
40+
}
3941
}
4042
}

src/model/model.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,21 @@ export abstract class Node extends Origin implements Destination {
205205
this[name].push(child.withParent(this));
206206
}
207207

208-
getChild(name: string, index?: number): Node | undefined {
208+
getChild(name: string | symbol, index?: number): Node | undefined {
209209
const containment = this.containment(name);
210210
if(!containment) {
211-
throw new Error("Not a containment: " + name);
211+
throw new Error("Not a containment: " + name.toString());
212212
}
213213
const raw = this.doGetChildOrChildren(name);
214214
if (containment.multiple) {
215215
if (index !== undefined) {
216216
return (raw as Node[])[index];
217217
} else {
218-
throw new Error(name + " is a collection, an index is required");
218+
throw new Error(name.toString() + " is a collection, an index is required");
219219
}
220220
} else {
221221
if (index) {
222-
throw new Error(name + " is not a collection, index " + index + " is invalid");
222+
throw new Error(name.toString() + " is not a collection, index " + index + " is invalid");
223223
} else {
224224
return raw as Node;
225225
}

src/trace/trace-node.ts

+35-17
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,33 @@ export abstract class NodeAdapter extends Node {
2020

2121
abstract getPosition(property?: string): Position | undefined;
2222

23-
abstract getRole(): string | undefined;
23+
getRole(): string | symbol | undefined {
24+
if (this.parent) { // Inefficient default implementation, searching in the parent's children
25+
const props = this.parent.nodeDefinition?.properties || {};
26+
for (const p in props) {
27+
const prop = props[p];
28+
if (prop.child) {
29+
if (prop.multiple) {
30+
if (this.parent.getChildren(prop.name)?.find(c => c.equals(this))) {
31+
return prop.name;
32+
}
33+
} else if (this.equals(this.parent.getChild(prop.name) as NodeAdapter)) {
34+
return prop.name;
35+
}
36+
}
37+
}
38+
} else {
39+
return undefined;
40+
}
41+
}
2442

2543
abstract isDeclaration(): boolean;
2644

2745
abstract isExpression(): boolean;
2846

2947
abstract isStatement(): boolean;
3048

31-
equals(other: NodeAdapter) {
49+
equals(other: NodeAdapter | undefined) {
3250
return other == this;
3351
}
3452
}
@@ -84,10 +102,6 @@ export class AugmentedNode extends NodeAdapter {
84102
return this.node.position;
85103
}
86104

87-
getRole(): string | undefined {
88-
return undefined;
89-
}
90-
91105
isDeclaration(): boolean {
92106
return false;
93107
}
@@ -99,13 +113,17 @@ export class AugmentedNode extends NodeAdapter {
99113
isStatement(): boolean {
100114
return false;
101115
}
116+
117+
equals(other: NodeAdapter | undefined): boolean {
118+
return other instanceof AugmentedNode && this.node == other.node;
119+
}
102120
}
103121

104122
export abstract class TraceNode extends Node {
105123

106124
abstract parent?: TraceNode;
107125

108-
protected constructor(public wrappedNode: NodeAdapter) {
126+
protected constructor(public nodeAdapter: NodeAdapter) {
109127
super();
110128
}
111129

@@ -121,28 +139,28 @@ export abstract class TraceNode extends Node {
121139
return this.nodeDefinition.name!;
122140
}
123141

124-
getRole(): string | undefined {
125-
return this.wrappedNode.getRole();
142+
getRole(): string | symbol | undefined {
143+
return this.nodeAdapter.getRole();
126144
}
127145

128146
getPosition(): Position | undefined {
129-
return this.wrappedNode.getPosition();
147+
return this.nodeAdapter.getPosition();
130148
}
131149

132150
get position(): Position | undefined {
133151
return this.getPosition();
134152
}
135153

136154
get nodeDefinition(): NodeDefinition {
137-
return this.wrappedNode.nodeDefinition;
155+
return this.nodeAdapter.nodeDefinition;
138156
}
139157

140158
getAttributes(): { [name: string]: any } {
141-
return this.wrappedNode.getAttributes();
159+
return this.nodeAdapter.getAttributes();
142160
}
143161

144162
doGetAttribute(attrName: string): any {
145-
return this.wrappedNode.getAttribute(attrName);
163+
return this.nodeAdapter.getAttribute(attrName);
146164
}
147165

148166
getPathFromRoot(): (string | number)[] {
@@ -167,18 +185,18 @@ export abstract class TraceNode extends Node {
167185
}
168186

169187
equals(node: TraceNode) {
170-
return node === this || node.wrappedNode.equals(this.wrappedNode);
188+
return node === this || node.nodeAdapter.equals(this.nodeAdapter);
171189
}
172190

173191
isDeclaration(): boolean {
174-
return this.wrappedNode.isDeclaration();
192+
return this.nodeAdapter.isDeclaration();
175193
}
176194

177195
isExpression(): boolean {
178-
return this.wrappedNode.isExpression();
196+
return this.nodeAdapter.isExpression();
179197
}
180198

181199
isStatement(): boolean {
182-
return this.wrappedNode.isStatement();
200+
return this.nodeAdapter.isStatement();
183201
}
184202
}

tests/ecore.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ECore from "ecore/dist/ecore";
1414
import * as fs from "fs";
1515
import {KOLASU_URI_V1} from "../src/interop/kolasu-v1-metamodel";
1616
import {THE_POSITION_ECLASS, THE_AST_RESOURCE} from "../src/interop/kolasu-v1-metamodel";
17+
import {STARLASU_URI_V2} from "../src/interop/starlasu-v2-metamodel";
1718

1819
describe('Metamodel', function() {
1920
it("Base metamodel", function () {
@@ -141,6 +142,19 @@ describe("Import/export", function () {
141142
// console.log(string);
142143
});
143144
});
145+
it("fails on unknown eClass", function () {
146+
const resourceSet = ECore.ResourceSet.create();
147+
const resource = resourceSet.create({ uri: 'file:dummy.json' });
148+
expect(() => loadEObject({ eClass: "DoesNotExist" }, resource)).to.throw(
149+
Error, "Unsupported class name: DoesNotExist"
150+
);
151+
expect(() => loadEObject({ eClass: "unknown.pkg#DoesNotExist" }, resource)).to.throw(
152+
Error, "Package not found: unknown.pkg while loading class unknown.pkg#DoesNotExist"
153+
);
154+
expect(() => loadEObject({ eClass: `${STARLASU_URI_V2}#DoesNotExist` }, resource)).to.throw(
155+
Error, "Unknown EClass: https://strumenta.com/starlasu/v2#DoesNotExist"
156+
);
157+
});
144158
it("importing using API", function () {
145159
const resourceSet = ECore.ResourceSet.create();
146160
const resource = resourceSet.create({ uri: 'file:data/sas.metamodel.json' });

tests/interop/lionweb.test.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,20 @@ describe('Lionweb integration', function() {
105105
const root = nodes[0];
106106
expect(root.node).to.be.instanceof(LionwebNode);
107107
let dir = new ParserNode(root.node as LionwebNode, undefined, new ParserTrace(PARSER_TRACE_ECLASS.create({}))); // TODO
108+
expect(dir.getRole()).to.be.undefined;
108109
expect(dir.nodeDefinition.name).to.equal("Directory");
109110
expect(dir.getAttribute("name")).to.equal("resources.zip");
110111
expect(dir.getChildren("files").length).to.equal(1);
111112
dir = dir.getChildren("files")[0];
112113
expect(dir.nodeDefinition.name).to.equal("Directory");
113114
expect(dir.getAttribute("name")).to.equal("resources");
114115
expect(dir.getChildren("files").length).to.equal(15);
115-
const file = dir.getChildren("files")[0];
116+
const file = dir.getChildren("files")[1];
116117
expect(file.nodeDefinition.name).to.equal("TextFile");
117-
expect(file.getAttribute("name")).to.equal("delegate.egl");
118-
expect(file.getAttribute("contents").substring(0, 10)).to.equal("Delegate F");
119-
expect(file.getPathFromRoot()).to.equal([]);
118+
expect(file.getAttribute("name")).to.equal("rosetta-code-count-examples-2.egl");
119+
expect(file.getAttribute("contents").substring(0, 10)).to.equal("package co");
120+
expect(file.getRole()).to.equal("files");
121+
expect(file.getPathFromRoot()).to.eql(["files", 0, "files", 1]);
120122

121123
expect(printSequence(walk(root.node))).to.equal(
122124
"resources.zip, resources, delegate.egl, rosetta-code-count-examples-2.egl, " +

0 commit comments

Comments
 (0)