From 3c70b75dbcedc9731d8c04f9e6d5efa64b6fe44e Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 12:53:08 -0700 Subject: [PATCH 01/13] add plantuml package and start adding the ast classes / interfaces --- src/main/java/org/plantuml/ast/PlantumlConnection.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlExpr.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlExprVisitor.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlId.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlObject.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlProgram.java | 4 ++++ src/main/java/org/plantuml/ast/PlantumlProperty.java | 4 ++++ 7 files changed, 28 insertions(+) create mode 100644 src/main/java/org/plantuml/ast/PlantumlConnection.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlExpr.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlExprVisitor.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlId.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlObject.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlProgram.java create mode 100644 src/main/java/org/plantuml/ast/PlantumlProperty.java diff --git a/src/main/java/org/plantuml/ast/PlantumlConnection.java b/src/main/java/org/plantuml/ast/PlantumlConnection.java new file mode 100644 index 00000000..c503c6ee --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlConnection.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public class PlantumlConnection { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlExpr.java b/src/main/java/org/plantuml/ast/PlantumlExpr.java new file mode 100644 index 00000000..1b254527 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlExpr.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public interface PlantumlExpr { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java new file mode 100644 index 00000000..b7d5ffb4 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public interface PlantumlExprVisitor { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlId.java b/src/main/java/org/plantuml/ast/PlantumlId.java new file mode 100644 index 00000000..dc9e335a --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlId.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public interface PlantumlId { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlObject.java b/src/main/java/org/plantuml/ast/PlantumlObject.java new file mode 100644 index 00000000..ce8278f1 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlObject.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public class PlantumlObject { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java new file mode 100644 index 00000000..66df6f30 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public class PlantumlProgram { +} diff --git a/src/main/java/org/plantuml/ast/PlantumlProperty.java b/src/main/java/org/plantuml/ast/PlantumlProperty.java new file mode 100644 index 00000000..0e1fad62 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlProperty.java @@ -0,0 +1,4 @@ +package org.plantuml.ast; + +public class PlantumlProperty { +} From 1044332e57bb0b0b081489000f3610f95f6abfaf Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 13:32:08 -0700 Subject: [PATCH 02/13] add plantuml to cli inputs and check for conflicting options --- src/main/java/org/clafer/cli/Main.java | 3 ++- src/main/java/org/clafer/cli/Normal.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/clafer/cli/Main.java b/src/main/java/org/clafer/cli/Main.java index e7489c64..90f72721 100644 --- a/src/main/java/org/clafer/cli/Main.java +++ b/src/main/java/org/clafer/cli/Main.java @@ -26,11 +26,12 @@ public static void main(String[] args) throws Exception { accepts( "n", "Specify the maximum number of instances." ).withRequiredArg().ofType( Integer.class ); accepts( "noprint", "Don't print the instances to the console or a file"); accepts( "output", "Output instances to the given file." ).withRequiredArg().ofType( File.class ).describedAs( "text file" ); + accepts( "plantuml", "Print the clafer model as PlantUML" ); accepts( "prettify", "Use simple and pretty output format (not formal)." ); - accepts( "sysml", "Print the instances as SysMLv2" ); accepts( "repl", "Run in REPL (interactive) mode." ); accepts( "scope", "Override the default global scope value." ).withRequiredArg().ofType( Integer.class ); accepts( "search", "PreferSmallerInstances/PreferLargerInstances/Random" ).withRequiredArg().ofType( ClaferSearchStrategy.class ); + accepts( "sysml", "Print the instances as SysMLv2" ); accepts( "time", "Time how long it takes to find all instances (and print if it is turned on"); accepts( "v", "Run in validation mode; checks all assertions." ); accepts( "version", "Display the tool version" ); diff --git a/src/main/java/org/clafer/cli/Normal.java b/src/main/java/org/clafer/cli/Normal.java index 50778f46..6caa4cbd 100644 --- a/src/main/java/org/clafer/cli/Normal.java +++ b/src/main/java/org/clafer/cli/Normal.java @@ -45,9 +45,17 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, int index = 0; // instance id boolean prettify = options.has("prettify"); boolean sysml = options.has("sysml"); + boolean plantuml = options.has("plantuml"); boolean printOff = options.has("noprint"); boolean dataTackingOn = options.has("dataFile"); boolean timeOn = options.has("time"); + + // check for conflicting options + if (plantuml && sysml) { + System.err.println("Bad CLI config: both plantuml and sysml are selected"); + return; + } + File dataFile; PrintStream dataStream = null; if (dataTackingOn) { @@ -65,6 +73,8 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, else n = -1; + + while (solver.find()) { if (dataTackingOn) { elapsedTime = (double) (System.nanoTime() - startTime) / 1000000000; From 9205f45d38f0e861e9396d5a975fbef488ed01b0 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 13:56:11 -0700 Subject: [PATCH 03/13] flesh out compile and pprinter interfaces --- src/main/java/org/clafer/cli/Normal.java | 14 +++++++-- .../java/org/plantuml/ast/PlantumlExpr.java | 12 ++++++++ .../org/plantuml/ast/PlantumlExprVisitor.java | 11 ++++++- .../java/org/plantuml/ast/PlantumlId.java | 6 ++++ .../org/plantuml/ast/PlantumlProgram.java | 23 ++++++++++++++- .../compiler/AstPlantumlCompiler.java | 16 ++++++++++ .../plantuml/pprinter/PlantumlPrinter.java | 29 +++++++++++++++++++ 7 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java create mode 100644 src/main/java/org/plantuml/pprinter/PlantumlPrinter.java diff --git a/src/main/java/org/clafer/cli/Normal.java b/src/main/java/org/clafer/cli/Normal.java index 6caa4cbd..8502c3b2 100644 --- a/src/main/java/org/clafer/cli/Normal.java +++ b/src/main/java/org/clafer/cli/Normal.java @@ -13,6 +13,9 @@ import org.clafer.objective.Objective; import org.clafer.scope.Scope; import org.clafer.ast.AstModel; +import org.plantuml.ast.PlantumlProgram; +import org.plantuml.compiler.AstPlantumlCompiler; +import org.plantuml.pprinter.PlantumlPrinter; import org.sysml.ast.SysmlProperty; import org.sysml.ast.SysmlPropertyDef; import org.sysml.compiler.AstSysmlCompiler; @@ -63,6 +66,15 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, dataStream = new PrintStream(dataFile); } + if (plantuml) { + AstModel top = javascriptFile.getModel(); + AstPlantumlCompiler compiler = new AstPlantumlCompiler(); + PlantumlProgram prog = compiler.compile(top); + PlantumlPrinter pprinter = new PlantumlPrinter(outStream); + pprinter.visit(prog, ""); + return; + } + double elapsedTime; long startTime = System.nanoTime(); @@ -73,8 +85,6 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, else n = -1; - - while (solver.find()) { if (dataTackingOn) { elapsedTime = (double) (System.nanoTime() - startTime) / 1000000000; diff --git a/src/main/java/org/plantuml/ast/PlantumlExpr.java b/src/main/java/org/plantuml/ast/PlantumlExpr.java index 1b254527..c32e48da 100644 --- a/src/main/java/org/plantuml/ast/PlantumlExpr.java +++ b/src/main/java/org/plantuml/ast/PlantumlExpr.java @@ -1,4 +1,16 @@ package org.plantuml.ast; +import java.io.IOException; + public interface PlantumlExpr { + /** + * Dynamic dispatch on the visitor. + * + * @param the parameter type + * @param the return type + * @param visitor the visitor + * @param a the parameter + * @return the return value + */ + B accept(PlantumlExprVisitor visitor, A a) throws IOException; } diff --git a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java index b7d5ffb4..ec1e1b14 100644 --- a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java +++ b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java @@ -1,4 +1,13 @@ package org.plantuml.ast; -public interface PlantumlExprVisitor { +import java.io.IOException; + +/** + * AST Visitor + * + * We make AST visitors capable of throwing IOExecptions as it's convenient for pretty printers + * However, we could likely get rid of this throw some type of interface conversion. + */ +public interface PlantumlExprVisitor { + B visit(PlantumlProgram plantumlProgram, A a) throws IOException; } diff --git a/src/main/java/org/plantuml/ast/PlantumlId.java b/src/main/java/org/plantuml/ast/PlantumlId.java index dc9e335a..f1be4665 100644 --- a/src/main/java/org/plantuml/ast/PlantumlId.java +++ b/src/main/java/org/plantuml/ast/PlantumlId.java @@ -1,4 +1,10 @@ package org.plantuml.ast; public interface PlantumlId { + /** + * PlantUML + * + * @return the name of the identifier + */ + String getName(); } diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java index 66df6f30..e7f097c4 100644 --- a/src/main/java/org/plantuml/ast/PlantumlProgram.java +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -1,4 +1,25 @@ package org.plantuml.ast; -public class PlantumlProgram { +import org.sysml.ast.SysmlExpr; +import org.sysml.ast.SysmlExprVisitor; + +import java.io.IOException; + +/** + * Main PlantUML Program Element + * + * This is likely quite wrong. For our use case, we consider a PlantUML program as a collection + * of objects and connections. + */ +public class PlantumlProgram implements PlantumlExpr { + private PlantumlObject[] objects; + private PlantumlConnection[] connections; + + public PlantumlProgram() { + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } } diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java new file mode 100644 index 00000000..e778d975 --- /dev/null +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -0,0 +1,16 @@ +package org.plantuml.compiler; + +import org.clafer.ast.AstModel; +import org.plantuml.ast.PlantumlProgram; + +/** + * Clafer AST to PlantUML + * + * Note that this compilation doesn't require instances, so we don't need to run the solver + * to compile. + */ +public class AstPlantumlCompiler { + public PlantumlProgram compile(AstModel model) { + return new PlantumlProgram(); + } +} diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java new file mode 100644 index 00000000..68a871e9 --- /dev/null +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -0,0 +1,29 @@ +package org.plantuml.pprinter; + +import org.plantuml.ast.PlantumlExprVisitor; +import org.plantuml.ast.PlantumlProgram; + +import java.io.IOException; + +/** + * PlantUML -> Text + * + * Visits the PlantUML AST and generates text output to an Appendable stream + */ +public class PlantumlPrinter implements PlantumlExprVisitor { + private final String indentBase; + private final Appendable out; + + public PlantumlPrinter(Appendable out) { + this.out = out; + this.indentBase = " "; + } + + // implement the visitor + @Override + public Void visit(PlantumlProgram ast, String indent) throws IOException { + this.out.append(indent).append("@startuml").append("\n"); + this.out.append(indent).append("@enduml").append("\n"); + return null; + } +} From 204c10251a10d5f9ed74629f52cc8aba6038707e Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 14:09:32 -0700 Subject: [PATCH 04/13] add fields to the plantuml ast --- .../org/plantuml/ast/PlantumlExprVisitor.java | 6 ++++ .../java/org/plantuml/ast/PlantumlObject.java | 25 ++++++++++++++++- .../org/plantuml/ast/PlantumlProgram.java | 8 ++++++ .../org/plantuml/ast/PlantumlProperty.java | 18 +++++++++++- .../plantuml/ast/PlantumlPropertyGroup.java | 28 +++++++++++++++++++ .../plantuml/pprinter/PlantumlPrinter.java | 18 ++++++++++-- 6 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java diff --git a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java index ec1e1b14..abc29980 100644 --- a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java +++ b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java @@ -10,4 +10,10 @@ */ public interface PlantumlExprVisitor { B visit(PlantumlProgram plantumlProgram, A a) throws IOException; + + B visit(PlantumlObject plantumlObject, A a) throws IOException; + + B visit(PlantumlPropertyGroup plantumlPropertyGroup, A a) throws IOException; + + B visit(PlantumlProperty plantumlProperty, A a) throws IOException; } diff --git a/src/main/java/org/plantuml/ast/PlantumlObject.java b/src/main/java/org/plantuml/ast/PlantumlObject.java index ce8278f1..24bebce8 100644 --- a/src/main/java/org/plantuml/ast/PlantumlObject.java +++ b/src/main/java/org/plantuml/ast/PlantumlObject.java @@ -1,4 +1,27 @@ package org.plantuml.ast; -public class PlantumlObject { +import java.io.IOException; + +public class PlantumlObject implements PlantumlExpr, PlantumlId { + private final String name; + private final PlantumlPropertyGroup[] propertyGroups; + + public PlantumlObject(String name, PlantumlPropertyGroup[] propertyGroups) { + this.name = name; + this.propertyGroups = propertyGroups; + } + + public PlantumlPropertyGroup[] getPropertyGroups() { + return propertyGroups; + } + + @Override + public String getName() { + return name; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } } diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java index e7f097c4..9d3da6ee 100644 --- a/src/main/java/org/plantuml/ast/PlantumlProgram.java +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -16,6 +16,14 @@ public class PlantumlProgram implements PlantumlExpr { private PlantumlConnection[] connections; public PlantumlProgram() { + this.objects = new PlantumlObject[0]; + this.connections = new PlantumlConnection[0]; + } + + public PlantumlProgram(PlantumlObject[] objects, PlantumlConnection[] connections) { + this.objects = objects; + this.connections = connections; + } @Override diff --git a/src/main/java/org/plantuml/ast/PlantumlProperty.java b/src/main/java/org/plantuml/ast/PlantumlProperty.java index 0e1fad62..ba85b23d 100644 --- a/src/main/java/org/plantuml/ast/PlantumlProperty.java +++ b/src/main/java/org/plantuml/ast/PlantumlProperty.java @@ -1,4 +1,20 @@ package org.plantuml.ast; -public class PlantumlProperty { +import java.io.IOException; + +public class PlantumlProperty implements PlantumlExpr { + private final String prop; + + public PlantumlProperty(String prop) { + this.prop = prop; + } + + public String getProp(){ + return prop; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } } diff --git a/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java b/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java new file mode 100644 index 00000000..87b8fe47 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java @@ -0,0 +1,28 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public class PlantumlPropertyGroup implements PlantumlId, PlantumlExpr { + + private final String name; + private PlantumlProperty[] properties; + + public PlantumlPropertyGroup(String name, PlantumlProperty[] properties) { + this.properties = properties; + this.name = name; + } + + public PlantumlProperty[] getProperties() { + return this.properties; + } + + @Override + public String getName() { + return name; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java index 68a871e9..8dbc2e7a 100644 --- a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -1,7 +1,6 @@ package org.plantuml.pprinter; -import org.plantuml.ast.PlantumlExprVisitor; -import org.plantuml.ast.PlantumlProgram; +import org.plantuml.ast.*; import java.io.IOException; @@ -26,4 +25,19 @@ public Void visit(PlantumlProgram ast, String indent) throws IOException { this.out.append(indent).append("@enduml").append("\n"); return null; } + + @Override + public Void visit(PlantumlObject plantumlObject, String s) throws IOException { + return null; + } + + @Override + public Void visit(PlantumlPropertyGroup plantumlPropertyGroup, String s) throws IOException { + return null; + } + + @Override + public Void visit(PlantumlProperty plantumlProperty, String s) throws IOException { + return null; + } } From 3ff20d6489ece636c0299eb8af50be1d25828dd9 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 17:15:36 -0700 Subject: [PATCH 05/13] pretty print objects --- .../org/plantuml/ast/PlantumlProgram.java | 4 + .../compiler/AstPlantumlCompiler.java | 97 ++++++++++++++++++- .../plantuml/pprinter/PlantumlPrinter.java | 18 ++++ 3 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java index 9d3da6ee..94991ba1 100644 --- a/src/main/java/org/plantuml/ast/PlantumlProgram.java +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -26,6 +26,10 @@ public PlantumlProgram(PlantumlObject[] objects, PlantumlConnection[] connection } + public PlantumlObject[] getObjects(){ + return objects; + } + @Override public B accept(PlantumlExprVisitor visitor, A a) throws IOException { return visitor.visit(this, a); diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index e778d975..307d8b08 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -1,7 +1,14 @@ package org.plantuml.compiler; +import org.clafer.ast.AstAbstractClafer; +import org.clafer.ast.AstConcreteClafer; +import org.clafer.ast.AstConstraint; import org.clafer.ast.AstModel; -import org.plantuml.ast.PlantumlProgram; +import org.plantuml.ast.*; +import org.sysml.compiler.SysmlCompilerUtils; + +import java.util.ArrayList; +import java.util.List; /** * Clafer AST to PlantUML @@ -10,7 +17,93 @@ * to compile. */ public class AstPlantumlCompiler { + /** + * collect all concrete clafers + * @param concreteClafers concreteClafers held in a claferModel + * @return ArrayList of all nested clafers (abstract included) + */ + private ArrayList getConcreteObjects(List concreteClafers) { + ArrayList objs = new ArrayList(); + + for (AstConcreteClafer ast: concreteClafers) { + ArrayList pgs = new ArrayList(); + + ArrayList constrs = new ArrayList(); + for (AstConstraint constr: ast.getConstraints()) { + constrs.add(new PlantumlProperty(constr.toString())); + } + + if (constrs.size() > 0){ + pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0]))); + } + + // create an object and add it + PlantumlObject obj = new PlantumlObject( + SysmlCompilerUtils.getPropertyId(ast.getName()), + pgs.toArray(new PlantumlPropertyGroup[0]) + ); + objs.add(obj); + + // add all of its children + // TODO: check for collisions? + //objs.addAll(getAbstractObjects(ast.getAbstractChildren())); + objs.addAll(getConcreteObjects(ast.getChildren())); + } + return objs; + } + + /** + * collect all abstract clafers (give them an abstract attribute) + * @param abstractClafers abstractClafers held in a claferModel + * @return ArrayList of all nested clafers (concrete included) + */ + private ArrayList getAbstractObjects(List abstractClafers) { + ArrayList objs = new ArrayList(); + + for (AstAbstractClafer ast: abstractClafers) { + ArrayList pgs = new ArrayList(); + + ArrayList constrs = new ArrayList(); + for (AstConstraint constr: ast.getConstraints()) { + constrs.add(new PlantumlProperty(constr.toString())); + } + + if (constrs.size() > 0){ + pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0]))); + } + + // create an object and add it + PlantumlObject obj = new PlantumlObject( + SysmlCompilerUtils.getPropertyId(ast.getName()), + pgs.toArray(new PlantumlPropertyGroup[0]) + ); + objs.add(obj); + + // add all of its children + // TODO: check for collisions? + objs.addAll(getAbstractObjects(ast.getAbstractChildren())); + objs.addAll(getConcreteObjects(ast.getChildren())); + } + return objs; + } + + /** + * top-level object collector + * @param model the root clafer model + * @return ArrayList of all clafers (abstract and concrete) suitable for PlantUML objects + */ + private ArrayList getObjects(AstModel model) { + ArrayList objs = getAbstractObjects(model.getAbstracts()); + objs.addAll(getConcreteObjects(model.getChildren())); + return objs; + } + public PlantumlProgram compile(AstModel model) { - return new PlantumlProgram(); + ArrayList objs = getObjects(model); + + return new PlantumlProgram( + objs.toArray(new PlantumlObject[0]), + new PlantumlConnection[0] + ); } } diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java index 8dbc2e7a..39f497a9 100644 --- a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -22,22 +22,40 @@ public PlantumlPrinter(Appendable out) { @Override public Void visit(PlantumlProgram ast, String indent) throws IOException { this.out.append(indent).append("@startuml").append("\n"); + for (PlantumlObject obj: ast.getObjects()){ + obj.accept(this, indent + indentBase); + } this.out.append(indent).append("@enduml").append("\n"); return null; } @Override public Void visit(PlantumlObject plantumlObject, String s) throws IOException { + this.out.append(s).append("object ").append(plantumlObject.getName()); + if (plantumlObject.getPropertyGroups().length > 0) { + this.out.append(" {\n"); + for (PlantumlPropertyGroup grp: plantumlObject.getPropertyGroups()){ + grp.accept(this, s + indentBase); + } + this.out.append(s).append("}\n"); + } else { + this.out.append("\n"); + } return null; } @Override public Void visit(PlantumlPropertyGroup plantumlPropertyGroup, String s) throws IOException { + this.out.append(s).append(".. ").append(plantumlPropertyGroup.getName()).append(" ..").append("\n"); + for (PlantumlProperty prop: plantumlPropertyGroup.getProperties()){ + prop.accept(this, s); + } return null; } @Override public Void visit(PlantumlProperty plantumlProperty, String s) throws IOException { + this.out.append(s).append(plantumlProperty.getProp()).append('\n'); return null; } } From 25138b5b615cfcc8e79fc1a31b38f693059fa451 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 17:49:30 -0700 Subject: [PATCH 06/13] add basic connections compilation --- .../org/plantuml/ast/PlantumlConnection.java | 43 +++++++++++- .../org/plantuml/ast/PlantumlExprVisitor.java | 2 + .../org/plantuml/ast/PlantumlProgram.java | 4 ++ .../compiler/AstPlantumlCompiler.java | 70 +++++++++++++++++-- .../plantuml/pprinter/PlantumlPrinter.java | 20 ++++++ 5 files changed, 134 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/plantuml/ast/PlantumlConnection.java b/src/main/java/org/plantuml/ast/PlantumlConnection.java index c503c6ee..cdf15c73 100644 --- a/src/main/java/org/plantuml/ast/PlantumlConnection.java +++ b/src/main/java/org/plantuml/ast/PlantumlConnection.java @@ -1,4 +1,45 @@ package org.plantuml.ast; -public class PlantumlConnection { +import java.io.IOException; + +public class PlantumlConnection implements PlantumlExpr { + private final String fromObj; + private final String toObj; + private final char fromConn; + private final char toConn; + + private final String label; + + public PlantumlConnection(String fromObj, String toObj, char fromConn, char toConn, String label){ + this.fromObj = fromObj; + this.toObj = toObj; + this.fromConn = fromConn; + this.toConn = toConn; + this.label = label; + } + + public String getFromObj(){ + return fromObj; + } + + public String getToObj(){ + return toObj; + } + + public char getToConn(){ + return toConn; + } + + public char getFromConn(){ + return fromConn; + } + + public String getLabel(){ + return label; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } } diff --git a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java index abc29980..115ef7f1 100644 --- a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java +++ b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java @@ -16,4 +16,6 @@ public interface PlantumlExprVisitor { B visit(PlantumlPropertyGroup plantumlPropertyGroup, A a) throws IOException; B visit(PlantumlProperty plantumlProperty, A a) throws IOException; + + B visit(PlantumlConnection plantumlConnection, A a) throws IOException; } diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java index 94991ba1..0cfad97d 100644 --- a/src/main/java/org/plantuml/ast/PlantumlProgram.java +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -30,6 +30,10 @@ public PlantumlObject[] getObjects(){ return objects; } + public PlantumlConnection[] getConnections(){ + return connections; + } + @Override public B accept(PlantumlExprVisitor visitor, A a) throws IOException { return visitor.visit(this, a); diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index 307d8b08..6dc1bc6c 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.WeakHashMap; /** * Clafer AST to PlantUML @@ -42,7 +43,10 @@ private ArrayList getConcreteObjects(List con SysmlCompilerUtils.getPropertyId(ast.getName()), pgs.toArray(new PlantumlPropertyGroup[0]) ); - objs.add(obj); + + if (!obj.getName().startsWith("#")) { + objs.add(obj); + } // add all of its children // TODO: check for collisions? @@ -77,7 +81,10 @@ private ArrayList getAbstractObjects(List abs SysmlCompilerUtils.getPropertyId(ast.getName()), pgs.toArray(new PlantumlPropertyGroup[0]) ); - objs.add(obj); + + if (!obj.getName().startsWith("#")){ + objs.add(obj); + } // add all of its children // TODO: check for collisions? @@ -98,12 +105,67 @@ private ArrayList getObjects(AstModel model) { return objs; } + private ArrayList getConcreteConnections(List abstractClafers) { + ArrayList connections = new ArrayList(); + + for (AstConcreteClafer ast: abstractClafers) { + String fromObj = SysmlCompilerUtils.getPropertyId(ast.getParent().getName()); + String toObj = SysmlCompilerUtils.getPropertyId(ast.getName()); + if (!(fromObj.startsWith("#") || toObj.startsWith("#"))) { + connections.add( + new PlantumlConnection( + fromObj, + toObj, + '-', + '*', + "" + ) + ); + } + + connections.addAll(getConcreteConnections(ast.getChildren())); + } + + return connections; + } + + private ArrayList getAbstractConnections(List abstractClafers) { + ArrayList connections = new ArrayList(); + + for (AstAbstractClafer ast: abstractClafers) { + String fromObj = SysmlCompilerUtils.getPropertyId(ast.getParent().getName()); + String toObj = SysmlCompilerUtils.getPropertyId(ast.getName()); + if (!(fromObj.startsWith("#") || toObj.startsWith("#"))) { + connections.add( + new PlantumlConnection( + fromObj, + toObj, + '-', + '*', + "" + ) + ); + } + + connections.addAll(getAbstractConnections(ast.getAbstractChildren())); + connections.addAll(getConcreteConnections(ast.getChildren())); + } + + return connections; + } + + private ArrayList getConnections(AstModel model) { + ArrayList connections = getAbstractConnections(model.getAbstracts()); + connections.addAll(getConcreteConnections(model.getChildren())); + return connections; + } + public PlantumlProgram compile(AstModel model) { ArrayList objs = getObjects(model); + ArrayList conns = getConnections(model); return new PlantumlProgram( - objs.toArray(new PlantumlObject[0]), - new PlantumlConnection[0] + objs.toArray(new PlantumlObject[0]), conns.toArray(new PlantumlConnection[0]) ); } } diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java index 39f497a9..bc6684e5 100644 --- a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -25,6 +25,10 @@ public Void visit(PlantumlProgram ast, String indent) throws IOException { for (PlantumlObject obj: ast.getObjects()){ obj.accept(this, indent + indentBase); } + this.out.append('\n'); + for (PlantumlConnection conn: ast.getConnections()){ + conn.accept(this, indent + indentBase); + } this.out.append(indent).append("@enduml").append("\n"); return null; } @@ -58,4 +62,20 @@ public Void visit(PlantumlProperty plantumlProperty, String s) throws IOExceptio this.out.append(s).append(plantumlProperty.getProp()).append('\n'); return null; } + + @Override + public Void visit(PlantumlConnection plantumlConnection, String s) throws IOException { + this.out.append(s) + .append(plantumlConnection.getFromObj()) + .append(" ").append(plantumlConnection.getFromConn()).append("-").append(plantumlConnection.getToConn()) + .append(" ") + .append(plantumlConnection.getToObj()); + if (plantumlConnection.getLabel().length() > 0){ + this.out.append(" ") + .append(": ") + .append(plantumlConnection.getLabel()); + } + this.out.append('\n'); + return null; + } } From b912367c28d55c171290ec229767d65af2ccf78a Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 18:11:37 -0700 Subject: [PATCH 07/13] add labels, support or and xor --- .../compiler/AstPlantumlCompiler.java | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index 6dc1bc6c..935e960e 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -1,9 +1,6 @@ package org.plantuml.compiler; -import org.clafer.ast.AstAbstractClafer; -import org.clafer.ast.AstConcreteClafer; -import org.clafer.ast.AstConstraint; -import org.clafer.ast.AstModel; +import org.clafer.ast.*; import org.plantuml.ast.*; import org.sysml.compiler.SysmlCompilerUtils; @@ -105,20 +102,40 @@ private ArrayList getObjects(AstModel model) { return objs; } - private ArrayList getConcreteConnections(List abstractClafers) { + private ArrayList getConcreteConnections(List concreteClafers) { ArrayList connections = new ArrayList(); - for (AstConcreteClafer ast: abstractClafers) { + for (AstConcreteClafer ast: concreteClafers) { String fromObj = SysmlCompilerUtils.getPropertyId(ast.getParent().getName()); String toObj = SysmlCompilerUtils.getPropertyId(ast.getName()); + String label = ""; + char toConn = '*'; + char fromConn = '-'; + if (ast.getParent().hasGroupCard()){ + if (ast.getParent().getGroupCard().toString().equals("1")){ + fromConn = '+'; + } else if (ast.getParent().getGroupCard().toString().equals("1..*")) { + fromConn = 'o'; + } + } + if (ast.getCard().toString().equals("0..1")){ + toConn = 'o'; + } else if (ast.getCard().toString().equals("1")) { + toConn = '-'; + } else { + if (ast.getCard().toString().startsWith("0")) { + toConn = 'o'; + } + label = ast.getCard().toString(); + } if (!(fromObj.startsWith("#") || toObj.startsWith("#"))) { connections.add( new PlantumlConnection( fromObj, toObj, - '-', - '*', - "" + fromConn, + toConn, + label ) ); } @@ -135,14 +152,24 @@ private ArrayList getAbstractConnections(List Date: Sun, 12 Mar 2023 18:15:11 -0700 Subject: [PATCH 08/13] close dataStream to properly handle dynamic allocation --- src/main/java/org/clafer/cli/Normal.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/clafer/cli/Normal.java b/src/main/java/org/clafer/cli/Normal.java index 8502c3b2..436a2777 100644 --- a/src/main/java/org/clafer/cli/Normal.java +++ b/src/main/java/org/clafer/cli/Normal.java @@ -72,6 +72,9 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, PlantumlProgram prog = compiler.compile(top); PlantumlPrinter pprinter = new PlantumlPrinter(outStream); pprinter.visit(prog, ""); + if (dataStream != null){ + dataStream.close(); + } return; } @@ -137,5 +140,10 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, System.out.println("Generated " + (n == -1 ? "all " : "") + index + " optimal instance(s) within the scope\n"); } } + + // make sure to close this resource + if (dataStream != null){ + dataStream.close(); + } } } From 1eaae9407092c4df31f0a596f8d4382c6fb689e4 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 18:19:47 -0700 Subject: [PATCH 09/13] make or arrowhead a filled shape --- src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index 935e960e..7ae2679e 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -115,7 +115,7 @@ private ArrayList getConcreteConnections(List getAbstractConnections(List Date: Sun, 12 Mar 2023 18:21:39 -0700 Subject: [PATCH 10/13] make mandatory arrowhead for multiplicity one features --- src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index 7ae2679e..ab8eddc9 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -121,7 +121,7 @@ private ArrayList getConcreteConnections(List Date: Sun, 12 Mar 2023 19:54:33 -0700 Subject: [PATCH 11/13] fold refs into object attributes --- .../org/plantuml/ast/PlantumlConnection.java | 16 +++++ .../compiler/AstPlantumlCompiler.java | 72 +++++++++++++++++-- .../plantuml/pprinter/PlantumlPrinter.java | 4 +- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/plantuml/ast/PlantumlConnection.java b/src/main/java/org/plantuml/ast/PlantumlConnection.java index cdf15c73..c1fed20d 100644 --- a/src/main/java/org/plantuml/ast/PlantumlConnection.java +++ b/src/main/java/org/plantuml/ast/PlantumlConnection.java @@ -8,6 +8,8 @@ public class PlantumlConnection implements PlantumlExpr { private final char fromConn; private final char toConn; + private final char lineChar; + private final String label; public PlantumlConnection(String fromObj, String toObj, char fromConn, char toConn, String label){ @@ -16,6 +18,16 @@ public PlantumlConnection(String fromObj, String toObj, char fromConn, char toCo this.fromConn = fromConn; this.toConn = toConn; this.label = label; + this.lineChar = '-'; + } + + public PlantumlConnection(String fromObj, String toObj, char fromConn, char toConn, String label, char lineChar){ + this.fromObj = fromObj; + this.toObj = toObj; + this.fromConn = fromConn; + this.toConn = toConn; + this.label = label; + this.lineChar = lineChar; } public String getFromObj(){ @@ -38,6 +50,10 @@ public String getLabel(){ return label; } + public char getLineChar(){ + return lineChar; + } + @Override public B accept(PlantumlExprVisitor visitor, A a) throws IOException { return visitor.visit(this, a); diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index ab8eddc9..6c648c1f 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.WeakHashMap; /** @@ -13,6 +14,8 @@ * * Note that this compilation doesn't require instances, so we don't need to run the solver * to compile. + * + * TODO: this should be refactored to cut down the code re-use. */ public class AstPlantumlCompiler { /** @@ -24,6 +27,9 @@ private ArrayList getConcreteObjects(List con ArrayList objs = new ArrayList(); for (AstConcreteClafer ast: concreteClafers) { + if (ast.getRef() != null) { + continue; + } ArrayList pgs = new ArrayList(); ArrayList constrs = new ArrayList(); @@ -62,6 +68,9 @@ private ArrayList getAbstractObjects(List abs ArrayList objs = new ArrayList(); for (AstAbstractClafer ast: abstractClafers) { + if (ast.getRef() != null) { + continue; + } ArrayList pgs = new ArrayList(); ArrayList constrs = new ArrayList(); @@ -69,6 +78,18 @@ private ArrayList getAbstractObjects(List abs constrs.add(new PlantumlProperty(constr.toString())); } + ArrayList refs = new ArrayList(); + for (AstConcreteClafer clafer: ast.getChildren()){ + AstRef ref = clafer.getRef(); + if (ref != null) { + refs.add(new PlantumlProperty(ref.toString())); + } + } + + if (refs.size() > 0){ + pgs.add(new PlantumlPropertyGroup("Attributes", refs.toArray(new PlantumlProperty[0]))); + } + if (constrs.size() > 0){ pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0]))); } @@ -106,8 +127,12 @@ private ArrayList getConcreteConnections(List connections = new ArrayList(); for (AstConcreteClafer ast: concreteClafers) { + if (ast.getRef() != null) { + continue; + } String fromObj = SysmlCompilerUtils.getPropertyId(ast.getParent().getName()); String toObj = SysmlCompilerUtils.getPropertyId(ast.getName()); + Card card = ast.getCard(); String label = ""; char toConn = '*'; char fromConn = '-'; @@ -118,15 +143,15 @@ private ArrayList getConcreteConnections(List getConcreteConnections(List', + "", + '.' + ) + ); + } + } + connections.addAll(getConcreteConnections(ast.getChildren())); } @@ -150,6 +193,9 @@ private ArrayList getAbstractConnections(List connections = new ArrayList(); for (AstAbstractClafer ast: abstractClafers) { + if (ast.getRef() != null) { + continue; + } String fromObj = SysmlCompilerUtils.getPropertyId(ast.getParent().getName()); String toObj = SysmlCompilerUtils.getPropertyId(ast.getName()); String label = ""; @@ -174,6 +220,24 @@ private ArrayList getAbstractConnections(List', + "", + '.' + ) + ); + } + } + connections.addAll(getAbstractConnections(ast.getAbstractChildren())); connections.addAll(getConcreteConnections(ast.getChildren())); } diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java index bc6684e5..be64cfc5 100644 --- a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -59,7 +59,7 @@ public Void visit(PlantumlPropertyGroup plantumlPropertyGroup, String s) throws @Override public Void visit(PlantumlProperty plantumlProperty, String s) throws IOException { - this.out.append(s).append(plantumlProperty.getProp()).append('\n'); + this.out.append(s).append("* ").append(plantumlProperty.getProp()).append('\n'); return null; } @@ -67,7 +67,7 @@ public Void visit(PlantumlProperty plantumlProperty, String s) throws IOExceptio public Void visit(PlantumlConnection plantumlConnection, String s) throws IOException { this.out.append(s) .append(plantumlConnection.getFromObj()) - .append(" ").append(plantumlConnection.getFromConn()).append("-").append(plantumlConnection.getToConn()) + .append(" ").append(plantumlConnection.getFromConn()).append(plantumlConnection.getLineChar()).append(plantumlConnection.getToConn()) .append(" ") .append(plantumlConnection.getToObj()); if (plantumlConnection.getLabel().length() > 0){ From ab5bea8b02a479eefee28ad01f4bc5147d2c4344 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 20:09:07 -0700 Subject: [PATCH 12/13] remove Optimizing message for plantuml (which does not optimize) --- src/main/java/org/clafer/cli/Normal.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/clafer/cli/Normal.java b/src/main/java/org/clafer/cli/Normal.java index 436a2777..4b528bb1 100644 --- a/src/main/java/org/clafer/cli/Normal.java +++ b/src/main/java/org/clafer/cli/Normal.java @@ -25,12 +25,16 @@ public class Normal { // Running the model itself(instantiating or optimizing) public static void runNormal(JavascriptFile javascriptFile, OptionSet options, PrintStream outStream) throws Exception { + //do this first to cut irrelevant optimizing message + boolean plantuml = options.has("plantuml"); Objective[] objectives = javascriptFile.getObjectives(); - if (objectives.length == 0) - System.out.println("Instantiating..."); - else - System.out.println("Optimizing..."); + if (!plantuml){ + if (objectives.length == 0) + System.out.println("Instantiating..."); + else + System.out.println("Optimizing..."); + } // handle scopes Scope scope = Utils.resolveScopes(javascriptFile, options); @@ -48,7 +52,6 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, int index = 0; // instance id boolean prettify = options.has("prettify"); boolean sysml = options.has("sysml"); - boolean plantuml = options.has("plantuml"); boolean printOff = options.has("noprint"); boolean dataTackingOn = options.has("dataFile"); boolean timeOn = options.has("time"); From 71e9e85788024c1c149b5cb947920c2f34945e99 Mon Sep 17 00:00:00 2001 From: Ethan Lew Date: Sun, 12 Mar 2023 20:12:48 -0700 Subject: [PATCH 13/13] fix confusing renaming assignment --- .../org/plantuml/compiler/AstPlantumlCompiler.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java index 6c648c1f..359dde4a 100644 --- a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -169,11 +169,12 @@ private ArrayList getConcreteConnections(List', "", @@ -224,11 +225,12 @@ private ArrayList getAbstractConnections(List', "",