Skip to content

Commit 039f47c

Browse files
committed
#60 improve Lionweb integration (deserialization only)
1 parent eab0004 commit 039f47c

File tree

5 files changed

+51
-29
lines changed

5 files changed

+51
-29
lines changed

src/interop/json.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ Node.prototype[TO_JSON_SYMBOL] = function (withIds?: Indexer) {
3333
}
3434
const element = node[p];
3535
if(element !== undefined && element !== null) {
36-
if(node.isChild(p)) {
37-
if(element instanceof Node) {
38-
result[p] = toJSON(element, withIds);
39-
} else if(Array.isArray(element)) {
36+
const containment = node.containment(p);
37+
if(containment) {
38+
if(containment.multiple) {
4039
result[p] = element.map(e => toJSON(e));
40+
} else {
41+
result[p] = toJSON(element, withIds);
4142
}
4243
}
4344
else if (element instanceof ReferenceByName) {
@@ -55,4 +56,4 @@ Node.prototype[TO_JSON_SYMBOL] = function (withIds?: Indexer) {
5556
}
5657
}
5758
return result;
58-
}
59+
}

src/interop/lionweb.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
Classifier,
33
Containment, deserializeLanguages,
4-
EnumerationLiteral,
54
Feature,
65
Id,
76
InstantiationFacade, Language,
@@ -27,7 +26,7 @@ export class LanguageMapping {
2726

2827
extend(languageMapping: LanguageMapping): this {
2928
const entries = languageMapping.nodeTypes.entries();
30-
for (let entry of entries) {
29+
for (const entry of entries) {
3130
this.nodeTypes.set(entry[0], entry[1]);
3231
this.classifiers.set(entry[1], entry[0]);
3332
}
@@ -39,14 +38,14 @@ export class TylasuInstantiationFacade implements InstantiationFacade<TylasuNode
3938

4039
constructor(public languageMappings: LanguageMapping[] = [STARLASU_LANGUAGE_MAPPING]) {}
4140

42-
encodingOf(literal: EnumerationLiteral): unknown {
41+
encodingOf(): unknown {
4342
return undefined;
4443
}
4544
nodeFor(parent: TylasuNode | undefined, classifier: Classifier, id: string, propertySettings: {
4645
[p: string]: unknown
4746
}): TylasuNode {
4847
let node: Node | undefined;
49-
for (let language of this.languageMappings) {
48+
for (const language of this.languageMappings) {
5049
const nodeType = language.classifiers.get(classifier);
5150
if (nodeType) {
5251
node = this.makeNode(nodeType, parent?.node, propertySettings);
@@ -66,7 +65,11 @@ export class TylasuInstantiationFacade implements InstantiationFacade<TylasuNode
6665
}
6766
setFeatureValue(node: TylasuNode, feature: Feature, value: unknown): void {
6867
if (feature instanceof Containment) {
69-
node.node.setChild(feature.name, (value as TylasuNode)?.node);
68+
if (feature.multiple) {
69+
node.node.addChild(feature.name, (value as TylasuNode)?.node);
70+
} else {
71+
node.node.setChild(feature.name, (value as TylasuNode)?.node);
72+
}
7073
} else {
7174
node.node[feature.name] = value;
7275
}

src/model/model.ts

+15-10
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,20 @@ export abstract class Node extends Origin implements Destination {
157157
});
158158
}
159159

160-
isChild(name: string): boolean {
161-
return this.getChildNames().indexOf(name) >= 0;
160+
containment(name: string): { multiple: boolean } | undefined {
161+
const props = this.nodeDefinition?.properties || {};
162+
if (name in props) {
163+
return props[name].child ? { multiple: !!props[name].multiple } : undefined;
164+
} else {
165+
return undefined;
166+
}
162167
}
163168

164169
setChild(name: string, child: Node): void {
165-
if(!this.isChild(name)) {
166-
throw new Error("Not a child: " + name);
167-
}
168-
if(Array.isArray(this[name])) {
170+
const containment = this.containment(name);
171+
if(!containment) {
172+
throw new Error("Not a containment: " + name);
173+
} else if (containment.multiple) {
169174
throw new Error(name + " is a collection, use addChild");
170175
}
171176
if(child.parent && child.parent != this) {
@@ -178,10 +183,10 @@ export abstract class Node extends Origin implements Destination {
178183
}
179184

180185
addChild(name: string, child: Node): void {
181-
if(!this.isChild(name)) {
182-
throw new Error("Not a child: " + name);
183-
}
184-
if(this[name] && !Array.isArray(this[name])) {
186+
const containment = this.containment(name);
187+
if(!containment) {
188+
throw new Error("Not a containment: " + name);
189+
} else if (!containment.multiple) {
185190
throw new Error(name + " is not a collection, use setChild");
186191
}
187192
if(child.parent && child.parent != this) {

tests/interop/lionweb.test.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FS_LANGUAGE_JSON from "./fs-language.json";
22
import FS_MODEL from "./fs-model.json";
3+
import {expect} from "chai";
34
import {deserializeChunk, deserializeLanguages, SerializationChunk} from "@lionweb/core";
45
import {Children, Node} from "../../src";
56
import {
@@ -34,6 +35,20 @@ describe('Lionweb integration', function() {
3435
it("Can deserialize simple model",
3536
function () {
3637
const nodes = deserializeChunk(FS_MODEL, new TylasuInstantiationFacade([FS_LANGUAGE_MAPPING]), [FS_LANGUAGE], []);
37-
expect(nodes).not.toBeFalsy();
38+
expect(nodes).not.to.be.empty;
39+
expect(nodes.length).to.equal(1);
40+
const root = nodes[0];
41+
expect(root.node).to.be.instanceof(Directory);
42+
let dir = root.node as Directory;
43+
expect(dir.name).to.equal("resources.zip");
44+
expect(dir.files.length).to.equal(1);
45+
expect(dir.files[0]).to.be.instanceof(Directory);
46+
dir = dir.files[0] as Directory;
47+
expect(dir.name).to.equal("resources");
48+
expect(dir.files.length).to.equal(15);
49+
expect(dir.files[0]).to.be.instanceof(TextFile);
50+
const file = dir.files[0] as TextFile;
51+
expect(file.name).to.equal("delegate.egl");
52+
expect(file.contents.substring(0, 10)).to.equal("Delegate F");
3853
});
3954
});

tests/json.test.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {expect} from "chai";
22

3-
import {ASTNode, Child, GenericNode, Node, PossiblyNamed, Property, ReferenceByName} from "../src";
3+
import {ASTNode, Child, Children, GenericNode, Node, PossiblyNamed, Property, ReferenceByName} from "../src";
44
import {JSONGenerator} from "../src";
55
import {Indexer} from "../src/interop/indexing";
66

@@ -183,18 +183,16 @@ describe('JSON generator', function() {
183183

184184
@ASTNode("", "NodeWithChildren")
185185
class NodeWithChildren extends Node {
186-
payload: number
186+
payload: number;
187187
@Child()
188-
singleChild: NodeWithChildren
189-
@Child()
190-
childrenCollection: NodeWithChildren[]
188+
singleChild: NodeWithChildren;
189+
@Children()
190+
childrenCollection: NodeWithChildren[];
191191
}
192192

193193
@ASTNode("", "DummyNamedNode")
194194
class DummyNamedNode extends Node implements PossiblyNamed {
195-
constructor(
196-
public name?: string
197-
) {
195+
constructor(public name?: string) {
198196
super();
199197
}
200198
}

0 commit comments

Comments
 (0)