diff --git a/mps-cli-ts/src/model/builder/root_node_builder.ts b/mps-cli-ts/src/model/builder/root_node_builder.ts index b04d11f..ac56f39 100644 --- a/mps-cli-ts/src/model/builder/root_node_builder.ts +++ b/mps-cli-ts/src/model/builder/root_node_builder.ts @@ -50,11 +50,12 @@ function populateNodePropertiesAndLinks(nodeElement : Element, node : SNode, imp let property = child const propertyRole = property.attributes.getNamedItem("role")!.value const propertyValue = property.attributes.getNamedItem("value")!.value - const nodeProperty = registry.getPropertyByIndex(propertyRole) + const nodeProperty = registry.getPropertyByRole(propertyRole) node.addProperty(nodeProperty, propertyValue) } else if(child.tagName === "ref") { let ref = child const refRole = ref.attributes.getNamedItem("role")!.value + const refResolveInfo = ref.attributes.getNamedItem("resolve")!.value var refNodeId : string; var modelId : string; @@ -69,7 +70,7 @@ function populateNodePropertiesAndLinks(nodeElement : Element, node : SNode, imp refNodeId = ref.attributes.getNamedItem("node")!.value } const refLink = registry.getLinkByIndex(refRole) - node.addLink(refLink, new SNodeRef(modelId, refNodeId)) + node.addLink(refLink, new SNodeRef(modelId, refNodeId, refResolveInfo)) } } } diff --git a/mps-cli-ts/src/model/builder/root_node_builder_fast.ts b/mps-cli-ts/src/model/builder/root_node_builder_fast.ts index 411b6bb..9bff2e0 100644 --- a/mps-cli-ts/src/model/builder/root_node_builder_fast.ts +++ b/mps-cli-ts/src/model/builder/root_node_builder_fast.ts @@ -48,12 +48,13 @@ function populateNodePropertiesAndLinks(node_json_element : any, node : SNode, i for(var property_json_element of ensure_array(node_json_element["property"])) { const propertyRole = property_json_element["@_role"] as string const propertyValue = property_json_element["@_value"] as string - const nodeProperty = registry.getPropertyByIndex(propertyRole) + const nodeProperty = registry.getPropertyByRole(propertyRole) node.addProperty(nodeProperty, propertyValue) } for(var ref_json_element of ensure_array(node_json_element["ref"])) { const refRole = ref_json_element["@_role"] as string + const refResolveInfo = ref_json_element["@_resolve"] as string var refNodeId : string; var modelId : string; @@ -68,7 +69,7 @@ function populateNodePropertiesAndLinks(node_json_element : any, node : SNode, i refNodeId = ref_json_element["@_node"] as string } const refLink = registry.getLinkByIndex(refRole) - node.addLink(refLink, new SNodeRef(modelId, refNodeId)) + node.addLink(refLink, new SNodeRef(modelId, refNodeId, refResolveInfo)) } } @@ -81,7 +82,7 @@ function buildRootNodeImports(imports_json_element : JsonElement) : SRootNodeImp const modelRef = model_import_json_element["@_ref"] as string const splits = modelRef.split("(") const modelId = splits[0] - const modelName = splits[1].substring(0, splits[1].length) + const modelName = splits[1].substring(0, splits[1].length - 1) const implicit = model_import_json_element["@_implicit"] as string imports.imports.push(new SModelImport(importIndex, modelId, modelName, implicit === "true")) @@ -103,7 +104,7 @@ function buildRootNodeRegistry(registry_json_element : JsonElement) : SRootNodeR const conceptId = registry_language_concept_json_element["@_id"] as string const conceptName = registry_language_concept_json_element["@_name"] as string - const conceptFlag = registry_language_concept_json_element["@_flag"] as string + const conceptFlag = registry_language_concept_json_element["@_flags"] as string const conceptIndex = registry_language_concept_json_element["@_index"] as string var myConcept = SLanguageBuilder.getConcept(myLanguage, conceptName, conceptId) diff --git a/mps-cli-ts/src/model/serializer/root_node_serializer.ts b/mps-cli-ts/src/model/serializer/root_node_serializer.ts new file mode 100644 index 0000000..29603b1 --- /dev/null +++ b/mps-cli-ts/src/model/serializer/root_node_serializer.ts @@ -0,0 +1,100 @@ +import { childrenByTagName } from "../builder/utils"; +import { SAbstractConceptLink, SChildLink, SProperty, SReferenceLink } from "../sconcept"; +import { SModel } from "../smodel"; +import { SNode, SNodeRef, SRootNode, SRootNodeImports, SRootNodeRegistry } from "../snode"; + + +export function serialize_root_node(model : SModel, root_node : SRootNode) : string { + var res = "" + res += "" + "\n" + res += `` + "\n" + var PADDING = " " + res += PADDING + "" + "\n" + + res += serialize_imports(root_node, PADDING); + res += serialize_registry(root_node, PADDING); + res += serialize_nodes(root_node.registry!, root_node.imports!, root_node, undefined, PADDING); + + res += "\n\n" + return res; +} + +function serialize_nodes(registry : SRootNodeRegistry, imports : SRootNodeImports, node : SNode, role : string | undefined, padding : string) : string { + var res = role === undefined ? + `${padding}\n` : + `${padding}\n` + padding = increase_indent(padding) + + for(const prop2Val of node.properties) { + res += `${padding}\n` + } + + for(const refLink of node.links.filter(it => it[0] instanceof SReferenceLink)) { + const refNode = (refLink[1][0] as SNodeRef) + res += `${padding}\n` + } + for(const childLink of node.links.filter(it => it[0] instanceof SChildLink)) { + for(const child of childLink[1]!) { + res += serialize_nodes(registry, imports, child as SNode, registry.getIndexForLink(childLink[0]), padding) + } + } + padding = decrease_indent(padding) + res += `${padding}\n` + return res; +} + +function serialize_registry(root_node : SRootNode, padding : string) : string { + var res = `${padding}\n` + padding = increase_indent(padding) + for(var languageRegistry of root_node.registry?.languages!) { + res += `${padding}\n` + padding = increase_indent(padding) + for(var concept of languageRegistry.usedConcepts) { + const noPropertiesAndLinks = concept.propertiesRegistries.length === 0 && concept.linksRegistries.length === 0 + res += `${padding}\n` + padding = increase_indent(padding) + for(var prop of concept.propertiesRegistries) { + res += `${padding}\n` + } + padding = decrease_indent(padding) + padding = increase_indent(padding) + for(var link of concept.linksRegistries) { + const linkKind = link.link instanceof SChildLink ? "child" : "reference" + res += `${padding}<${linkKind} id="${link.link.id}" name="${link.link.name}" index="${link.index}" />\n` + } + padding = decrease_indent(padding) + if (!noPropertiesAndLinks) { + res += `${padding}\n` + } + } + padding = decrease_indent(padding) + res += `${padding}\n` + } + padding = decrease_indent(padding) + res += `${padding}\n` + + return res; +} + +function serialize_imports(root_node : SRootNode, padding : string) : string { + if(root_node.imports?.imports.length == 0) { + return padding + "\n"; + } + + var res = padding + "\n"; + padding = increase_indent(padding) + for(var crtImport of root_node.imports?.imports!) { + res += `${padding}\n` + } + padding = decrease_indent(padding) + res += `${padding}\n` + return res; +} + +function increase_indent(padding : string) : string { + return padding + " " +} + +function decrease_indent(padding : string) : string { + return padding.substring(2) +} \ No newline at end of file diff --git a/mps-cli-ts/src/model/smodel.ts b/mps-cli-ts/src/model/smodel.ts index a08055d..a9283dd 100644 --- a/mps-cli-ts/src/model/smodel.ts +++ b/mps-cli-ts/src/model/smodel.ts @@ -30,4 +30,12 @@ export class SModel { return undefined } + findRootNodesByName(rootNodeName : string) : SRootNode[] { + const res : SRootNode[] = [] + for(const crtNode of this.rootNodes) { + if(crtNode.getProperty("name") === rootNodeName) + res.push(crtNode) + } + return res + } } \ No newline at end of file diff --git a/mps-cli-ts/src/model/snode.ts b/mps-cli-ts/src/model/snode.ts index 4391705..73e4512 100644 --- a/mps-cli-ts/src/model/snode.ts +++ b/mps-cli-ts/src/model/snode.ts @@ -5,8 +5,8 @@ import { SRepository } from "./srepository"; export class SNode { myConcept : SConcept; id : string; - links : Map = new Map - properties : Map = new Map + links : [SAbstractConceptLink, (SNode | SNodeRef)[]][] = [] + properties : [SProperty, string][] = [] myParent : SNode | undefined; constructor(myConcept : SConcept, id : string, parent : SNode | undefined) { @@ -16,30 +16,30 @@ export class SNode { } addLink(link : SAbstractConceptLink, node : SNode | SNodeRef) { - var nodesForLink = this.links.get(link); - if (nodesForLink == null) { - nodesForLink = []; - this.links.set(link, nodesForLink) + var nodesForLink = this.links.find(it => it[0] === link); + if (nodesForLink == undefined) { + this.links.push([link, [node]]) + } else { + nodesForLink[1].push(node) } - nodesForLink.push(node) } allLinkedNodes() : (SNode | SNodeRef)[] { var res : (SNode | SNodeRef)[] = [] - this.links.forEach((linkedNodes : (SNode | SNodeRef)[], link : SAbstractConceptLink) => { - res = res.concat(linkedNodes) + this.links.forEach(it => { + res = res.concat(it[1]) }) return res } addProperty(property : SProperty, value : string) { - this.properties.set(property, value); + this.properties.push([property, value]); } getProperty(propertyName : string) : string | undefined { - for(const prop of this.properties.keys()) - if(prop.name === propertyName) - return this.properties.get(prop); + for(const prop of this.properties) + if(prop[0].name === propertyName) + return prop[1]; return undefined } @@ -48,7 +48,7 @@ export class SNode { if (includeSelf && (concept === undefined || this.myConcept == concept)) { res.push(this) } const linksToVisit : [SChildLink, SNode[]][] = [] - const myChildren = Array.from(this.links.entries()).filter(it => it[0] instanceof SChildLink) + const myChildren = Array.from(this.links).filter(it => it[0] instanceof SChildLink) myChildren.forEach(it => linksToVisit.push(it as [SChildLink, SNode[]])) while(linksToVisit.length > 0) { @@ -56,7 +56,7 @@ export class SNode { res.push(...crtLink?.[1]) for(const childNode of crtLink?.[1]) { - const myChildren = Array.from(childNode.links.entries()).filter(it => it[0] instanceof SChildLink) + const myChildren = childNode.links.filter(it => it[0] instanceof SChildLink) myChildren.forEach(it => linksToVisit.push(it as [SChildLink, SNode[]])) } } @@ -65,9 +65,9 @@ export class SNode { getLinkedNodes(linkName : string) : (SNode | SNodeRef)[] { const res : (SNode | SNodeRef)[] = [] - this.links.forEach((linkedNodes : (SNode | SNodeRef)[], link : SAbstractConceptLink) => { - if (link.name === linkName) { - res.push(...linkedNodes) + this.links.forEach(it => { + if (it[0].name === linkName) { + res.push(...it[1]) } }); return res @@ -88,10 +88,12 @@ export class SRootNode extends SNode { export class SNodeRef { modelId : string nodeId : string + resolveInfo : string; - constructor(modelId : string, nodeId : string) { + constructor(modelId : string, nodeId : string, resolveInfo : string) { this.modelId = modelId this.nodeId = nodeId + this.resolveInfo = resolveInfo } resolve(repo : SRepository) : SNode | undefined { @@ -114,6 +116,14 @@ export class SRootNodeImports { return "undefined"; } + getModelIndexByModelId(modelId : string) : string { + for(const imp of this.imports) { + if (imp.myModelId === modelId) + return imp.myModelIndex + } + return "undefined"; + } + } export class SRootNodeRegistry { @@ -127,13 +137,34 @@ export class SRootNodeRegistry { return this.index2concepts.get(index)!; } + getIndexForConcept(concept : SConcept) : string | undefined { + for(const entry of this.index2concepts.entries()) + if(entry[1] === concept) + return entry[0] + return undefined; + } + getLinkByIndex(index : string) : SAbstractConceptLink { return this.index2links.get(index)!; } - getPropertyByIndex(index : string) : SProperty { + getIndexForLink(link : SAbstractConceptLink) : string | undefined { + for(const entry of this.index2links.entries()) + if(entry[1] === link) + return entry[0] + return undefined; + } + + getPropertyByRole(index : string) : SProperty { return this.index2properties.get(index)!; } + + getRoleForProperty(property : SProperty) : string | undefined { + for(const entry of this.index2properties.entries()) + if(entry[1] === property) + return entry[0] + return undefined; + } } export class SModelImport { diff --git a/mps-cli-ts/src/model/srepository.ts b/mps-cli-ts/src/model/srepository.ts index 66f876b..385e091 100644 --- a/mps-cli-ts/src/model/srepository.ts +++ b/mps-cli-ts/src/model/srepository.ts @@ -1,6 +1,6 @@ import { SModel } from "./smodel"; import { SAbstractModule } from "./smodule"; -import { SNode } from "./snode"; +import { SNode, SRootNode } from "./snode"; export class SRepository { @@ -60,7 +60,7 @@ export class SRepository { return undefined; } - findModelByName(modelName : string) : SModel[] { + findModelsByName(modelName : string) : SModel[] { const res : SModel[] = [] for(const crtModule of this.modules) { for(const crtModel of crtModule.models) { @@ -71,5 +71,18 @@ export class SRepository { return res; } + findRootNodesByName(rootNodeName : string) : SRootNode[] { + const res : SRootNode[] = [] + for(const crtModule of this.modules) { + for(const crtModel of crtModule.models) { + for(const crtRoot of crtModel.rootNodes) { + if (crtRoot.getProperty("name") === rootNodeName) + res.push(crtRoot) + } + } + } + return res; + } + } diff --git a/mps-cli-ts/tests/node.spec.ts b/mps-cli-ts/tests/node.spec.ts index 806c0e5..660710b 100644 --- a/mps-cli-ts/tests/node.spec.ts +++ b/mps-cli-ts/tests/node.spec.ts @@ -43,13 +43,12 @@ describe('testing building of root nodes', () => { assert.equal(rootNode.myConcept.name, "mps.cli.landefs.people.structure.PersonsContainer") assert.equal(rootNode.id, "4Yb5JA31NUu") assert.equal(rootNode.allLinkedNodes().length, 2) - assert.equal(rootNode.properties.size, 1) - const propertiesKeysArray = Array.from(rootNode.properties.keys()); + assert.equal(rootNode.properties.length, 1) - const nameProperty = propertiesKeysArray[0] + const nameProperty = rootNode.properties[0][0] assert.equal(nameProperty.name, "name"); assert.equal(nameProperty.id, "1169194664001"); - assert.equal(rootNode.properties.get(nameProperty), "_010_classical_authors"); + assert.equal(rootNode.getProperty(nameProperty.name), "_010_classical_authors"); assert.equal(rootNode.getProperty("name"), "_010_classical_authors"); const descendants = rootNode.descendants(undefined, false) diff --git a/mps-cli-ts/tests/project.spec.ts b/mps-cli-ts/tests/project.spec.ts index 86b45d2..ed3629c 100644 --- a/mps-cli-ts/tests/project.spec.ts +++ b/mps-cli-ts/tests/project.spec.ts @@ -14,9 +14,9 @@ describe("testing building of the model from a directory with solutions", () => assert.equal(repo.modules.length, 2); assert.equal(repo.getNodesWithPropertyAndValue("name").length, 11) - assert.equal(repo.findModelByName("mps.cli.lanuse.library_second.library_top").length, 1) - assert.equal(repo.findModelByName("mps.cli.lanuse.library_second.library_top")[0].rootNodes.length, 1) - assert.equal(repo.findModelByName("mps.cli.lanuse.library_second.library_top")[0].rootNodes[0].getProperty("name"), "_library") + assert.equal(repo.findModelsByName("mps.cli.lanuse.library_second.library_top").length, 1) + assert.equal(repo.findModelsByName("mps.cli.lanuse.library_second.library_top")[0].rootNodes.length, 1) + assert.equal(repo.findModelsByName("mps.cli.lanuse.library_second.library_top")[0].rootNodes[0].getProperty("name"), "_library") assert.equal(repo.getNodesWithPropertyAndValue("name", "_library").length, 1) assert.equal(repo.getNodesWithPropertyAndValue("name", "Mark Twain").length, 1) const markTwain = repo.getNodesWithPropertyAndValue("name", "Mark Twain")[0] diff --git a/mps-cli-ts/tests/root_node_serializer.spec.ts b/mps-cli-ts/tests/root_node_serializer.spec.ts new file mode 100644 index 0000000..2461515 --- /dev/null +++ b/mps-cli-ts/tests/root_node_serializer.spec.ts @@ -0,0 +1,59 @@ + +import { assert } from 'chai' +import { readdirSync, readFileSync, lstatSync } from 'fs' +import { loadSolutions, parseXml } from '../src/file_parser'; +import { buildRootNode } from '../src/model/builder/root_node_builder'; +import { SModel } from '../src/model/smodel'; +import { SRootNode } from '../src/model/snode'; +import { SRepository } from '../src/model/srepository'; +import { serialize_root_node } from '../src/model/serializer/root_node_serializer'; + + +describe('testing serializing the root node', () => { + + const projectPath = "../mps_test_projects/mps_cli_lanuse_file_per_root" + const repo: SRepository = loadSolutions(projectPath); + const library_second_library_top_model = repo.findModelsByName("mps.cli.lanuse.library_second.library_top")[0] + const library_top_library_top_model = repo.findModelsByName("mps.cli.lanuse.library_top.library_top")[0] + const library_top_authors_top_model = repo.findModelsByName("mps.cli.lanuse.library_top.authors_top")[0] + + it('serialize _library root node', () => { + const library = library_second_library_top_model.findRootNodesByName("_library")[0] + const serializedLibrary = serialize_root_node(library_second_library_top_model, library) + compare(projectPath + "/solutions/mps.cli.lanuse.library_second/models/mps.cli.lanuse.library_second.library_top/_library.mpsr", serializedLibrary) + }) + + it('serialize munich_library root node', () => { + const munich_library = library_top_library_top_model.findRootNodesByName("munich_library")[0] + const serializedLibrary = serialize_root_node(library_top_library_top_model, munich_library) + compare(projectPath + "/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/munich_library.mpsr", serializedLibrary) + }) + + it('serialize schwabing_library root node', () => { + const schwabing_library = library_top_library_top_model.findRootNodesByName("schwabing library")[0] + const serializedLibrary = serialize_root_node(library_top_library_top_model, schwabing_library) + compare(projectPath + "/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/schwabing library.mpsr", serializedLibrary) + }) + + it('serialize classical_authors root node', () => { + const classical_authors = library_top_authors_top_model.findRootNodesByName("_010_classical_authors")[0] + const serializedLibrary = serialize_root_node(library_top_authors_top_model, classical_authors) + compare(projectPath + "/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.authors_top/_010_classical_authors.mpsr", serializedLibrary) + }) + + + +}) + + + +function compare(filePathWithExpectedResult : string, serializedString : string) { + const expectedString = readFileSync(filePathWithExpectedResult, 'utf8') + const expectedLines = expectedString.split(/\r?\n/) + const actualLines = serializedString.split(/\r?\n/) + + for(let i = 0; i < Math.min(expectedLines.length, actualLines.length); i++) { + assert.equal(actualLines.at(i), expectedLines.at(i), `line ${i} is different`) + } + assert.equal(actualLines.length, expectedLines.length, `different number of lines:\n\texpected: ${expectedLines.length}\n\tactual: ${actualLines.length}`) +} \ No newline at end of file diff --git a/mps-cli-ts/tests/sandbox.ts b/mps-cli-ts/tests/sandbox.ts index a1cb711..8be54a3 100644 --- a/mps-cli-ts/tests/sandbox.ts +++ b/mps-cli-ts/tests/sandbox.ts @@ -1,10 +1,10 @@ import { loadModelsFromSolution, loadSolutions, parseXml } from "../src/file_parser"; //const repo = loadSolutions("c:\\work\\E3_2.0_Solution\\solutions") -const repo = loadSolutions('C:\\work\\mbeddr.formal\\code\\tutorial-safety') +//const repo = loadSolutions('C:\\work\\mbeddr.formal\\code\\tutorial-safety') //console.log("starting..........") //loadModelsFromSolution("C:\\work\\mps-cli\\mps_test_projects\\mps_cli_lanuse_file_per_root\\solutions\\mps.cli.lanuse.library_second") -console.log(`number of modules: ${ repo.modules.length }`) -console.log(`number of models: ${ repo.allModels().length }`) -console.log(`number of nodes: ${ repo.allNodes().length }`) \ No newline at end of file +//console.log(`number of modules: ${ repo.modules.length }`) +//console.log(`number of models: ${ repo.allModels().length }`) +//console.log(`number of nodes: ${ repo.allNodes().length }`) \ No newline at end of file