diff --git a/package-lock.json b/package-lock.json index 6676639..966dd45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1568,7 +1568,8 @@ "node_modules/littoral-templates": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/littoral-templates/-/littoral-templates-0.2.2.tgz", - "integrity": "sha512-zap1WAedVwSaQh8Ak1al3BBByvR1XmZ5QSQrpVq5XKvMw113ng36SlYX3KRRChaccDCbLM5BugDu7DNaUuBgtg==" + "integrity": "sha512-zap1WAedVwSaQh8Ak1al3BBByvR1XmZ5QSQrpVq5XKvMw113ng36SlYX3KRRChaccDCbLM5BugDu7DNaUuBgtg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", diff --git a/packages/test/src/m3/.gitignore b/packages/test/src/m3/.gitignore new file mode 100644 index 0000000..f370dfe --- /dev/null +++ b/packages/test/src/m3/.gitignore @@ -0,0 +1 @@ +/languageWithEnum.actual.txt diff --git a/packages/test/src/m3/languageWithEnum.expected.txt b/packages/test/src/m3/languageWithEnum.expected.txt new file mode 100644 index 0000000..917f729 --- /dev/null +++ b/packages/test/src/m3/languageWithEnum.expected.txt @@ -0,0 +1,14 @@ +language WithEnum + version: 1 + entities (↓name): + + concept EnumHolder + features (↓name): + enumValue: MyEnum + + enumeration MyEnum + literals: + literal1 + literal2 + + diff --git a/packages/test/src/m3/textualizer.ts b/packages/test/src/m3/textualizer.test.ts similarity index 84% rename from packages/test/src/m3/textualizer.ts rename to packages/test/src/m3/textualizer.test.ts index 56ed7d2..cd9c90a 100644 --- a/packages/test/src/m3/textualizer.ts +++ b/packages/test/src/m3/textualizer.test.ts @@ -3,31 +3,21 @@ const {equal} = assert import {serializeNodes} from "@lionweb/core" import {genericAsTreeText, languageAsText} from "@lionweb/utilities" +import {readFileSync, writeFileSync} from "fs" import {languageWithEnum} from "../languages/with-enum.js" import {libraryExtractionFacade, libraryModel} from "../instances/library.js" -import {libraryLanguage} from "../languages/library.js"; +import {libraryLanguage} from "../languages/library.js" describe("LionCore-specific textual syntax", () => { it("textualize language with an enum as text", () => { + const actual = languageAsText(languageWithEnum) + writeFileSync("src/m3/languageWithEnum.actual.txt", actual) equal( - languageAsText(languageWithEnum), -`language WithEnum - version: 1 - entities (↓name): - - concept EnumHolder - features (↓name): - enumValue: MyEnum - - enumeration MyEnum - literals: - literal1 - literal2 - -` + actual, + readFileSync("src/m3/languageWithEnum.expected.txt", { encoding: "utf8" }) ) }) diff --git a/packages/utilities/README.md b/packages/utilities/README.md index d013671..a2fd881 100644 --- a/packages/utilities/README.md +++ b/packages/utilities/README.md @@ -26,7 +26,8 @@ It contains utilities on top of the `core` package, such as: ### 0.6.9 - not yet officially released -* Made `withoutAnnotations` _not_ modify the original serialization chunk. +* Make `withoutAnnotations` _not_ modify the original serialization chunk. +* (Use the `littoral-templates` package for textualization — of M2s, so far. This is a technical change, not a functional one, except for maybe some extra whitespace.) ### 0.6.8 diff --git a/packages/utilities/src/m3/textualizer.ts b/packages/utilities/src/m3/textualizer.ts index f35c755..b194e41 100644 --- a/packages/utilities/src/m3/textualizer.ts +++ b/packages/utilities/src/m3/textualizer.ts @@ -1,5 +1,6 @@ import { Annotation, + Classifier, Concept, Containment, Enumeration, @@ -16,38 +17,46 @@ import { SingleRef, unresolved } from "@lionweb/core" +import {asString, indentWith, NestedString} from "littoral-templates" -// TODO use littoral-templates? -const indent = (str: string) => - str.split("\n").map((line) => ` ${line}`).join("\n") - - -const descent = (ts: T[], separator: string): string => - nameSorted(ts).map((t) => indent(indent(asText(t)))).join(separator) +const indent1 = indentWith(" ")(1) const refAsText = (ref: SingleRef): string => ref === unresolved ? `???` : ref.name +const recurse = (ts: T[], header: string, func: (t: T) => NestedString = asText): NestedString => + ts.length === 0 + ? [] + : indent1([ + header, + indent1(ts.map(func)) + ]) + +const featuresOf = (classifier: Classifier): NestedString => + recurse(nameSorted(classifier.features), `features (↓name):`) -const asText = (node: M3Node): string => { +const asText = (node: M3Node): NestedString => { if (node instanceof Annotation) { - return `annotation ${node.name}${node.extends === undefined ? `` : ` extends ${refAsText(node.extends)}`}${node.implements.length === 0 ? `` : ` implements ${nameSorted(node.implements).map(nameOf).join(", ")}`}${node.features.length === 0 ? `` : ` - features (↓name): -${descent(node.features, "\n")}`}` + return [ + `annotation ${node.name}${node.extends === undefined ? `` : ` extends ${refAsText(node.extends)}`}${node.implements.length === 0 ? `` : ` implements ${nameSorted(node.implements).map(nameOf).join(", ")}`}`, + featuresOf(node) + ] } if (node instanceof Concept) { - return `${node.partition ? `<> ` : ``}${node.abstract ? `abstract ` : ``}concept ${node.name}${node.extends === undefined ? `` : ` extends ${refAsText(node.extends)}`}${node.implements.length === 0 ? `` : ` implements ${nameSorted(node.implements).map(nameOf).join(", ")}`}${node.features.length === 0 ? `` : ` - features (↓name): -${descent(node.features, "\n")}`}` + return [ + `${node.partition ? `<> ` : ``}${node.abstract ? `abstract ` : ``}concept ${node.name}${node.extends === undefined ? `` : ` extends ${refAsText(node.extends)}`}${node.implements.length === 0 ? `` : ` implements ${nameSorted(node.implements).map(nameOf).join(", ")}`}`, + featuresOf(node) + ] } if (node instanceof Interface) { - return `interface ${node.name}${node.extends.length === 0 ? `` : ` extends ${nameSorted(node.extends).map(nameOf).join(", ")}`}${node.features.length === 0 ? `` : ` - features (↓name): -${descent(node.features, "\n")}`}` + return [ + `interface ${node.name}${node.extends.length === 0 ? `` : ` extends ${nameSorted(node.extends).map(nameOf).join(", ")}`}`, + featuresOf(node) + ] } if (node instanceof Link) { @@ -55,9 +64,10 @@ ${descent(node.features, "\n")}`}` } if (node instanceof Enumeration) { - return `enumeration ${node.name}${node.literals.length === 0 ? `` : ` - literals: -${descent(node.literals, "\n")}`}` + return [ + `enumeration ${node.name}`, + recurse(node.literals, `literals:`) + ] } if (node instanceof EnumerationLiteral) { @@ -65,17 +75,17 @@ ${descent(node.literals, "\n")}`}` } if (node instanceof Language) { - return `language ${node.name} - version: ${node.version}${node.dependsOn.length > 0 - ? `\n dependsOn: -${node.dependsOn.map((language) => ` ${language.name} (${language.version})`).join("\n")} -` - : ``} - entities (↓name): - -${descent(node.entities, "\n\n")} - -` + return [ + `language ${node.name}`, + indent1([ + `version: ${node.version}`, + recurse(node.dependsOn, `dependsOn`, (language) => `${language.name} (${language.version}`), + `entities (↓name):`, + ``, + indent1(nameSorted(node.entities).map((entity) => [asText(entity), ``])) + ]), + `` + ] } if (node instanceof PrimitiveType) { @@ -91,8 +101,11 @@ ${descent(node.entities, "\n\n")} } -export const languageAsText = asText +export const languageAsText = (language: Language) => + asString(asText(language)) export const languagesAsText = (languages: Language[]): string => - nameSorted(languages).map(asText).join("\n\n") + asString( + nameSorted(languages).map((language) => [asText(language), ``]) + )